test_completer.py
1819 lines
| 62.0 KiB
| text/x-python
|
PythonLexer
MinRK
|
r16564 | # encoding: utf-8 | ||
"""Tests for the IPython tab-completion machinery.""" | ||||
# Copyright (c) IPython Development Team. | ||||
# Distributed under the terms of the Modified BSD License. | ||||
Fernando Perez
|
r2365 | |||
Fernando Perez
|
r3184 | import os | ||
Nikita Kniazev
|
r27042 | import pytest | ||
Fernando Perez
|
r2365 | import sys | ||
Matthias Bussonnier
|
r23358 | import textwrap | ||
Fernando Perez
|
r2855 | import unittest | ||
M Bussonnier
|
r28976 | import random | ||
Fernando Perez
|
r2365 | |||
M Bussonnier
|
r28800 | from importlib.metadata import version | ||
MinRK
|
r16564 | from contextlib import contextmanager | ||
Min RK
|
r21253 | from traitlets.config.loader import Config | ||
Min RK
|
r21718 | from IPython import get_ipython | ||
Fernando Perez
|
r2365 | from IPython.core import completer | ||
Thomas Kluyver
|
r16767 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory | ||
Thomas Kluyver
|
r5155 | from IPython.utils.generics import complete_object | ||
Joel Nothman
|
r15766 | from IPython.testing import decorators as dec | ||
M Bussonnier
|
r28976 | from IPython.core.latex_symbols import latex_symbols | ||
Fernando Perez
|
r2365 | |||
Matthias Bussonnier
|
r23358 | from IPython.core.completer import ( | ||
Matthias Bussonnier
|
r25101 | Completion, | ||
provisionalcompleter, | ||||
match_dict_keys, | ||||
_deduplicate_completions, | ||||
krassowski
|
r27912 | _match_number_in_dict_key_prefix, | ||
krassowski
|
r27775 | completion_matcher, | ||
SimpleCompletion, | ||||
CompletionContext, | ||||
M Bussonnier
|
r28976 | _unicode_name_compute, | ||
_UNICODE_RANGES, | ||||
Matthias Bussonnier
|
r25101 | ) | ||
Matthias Bussonnier
|
r23284 | |||
M Bussonnier
|
r28800 | from packaging.version import parse | ||
M Bussonnier
|
r28976 | @contextmanager | ||
def jedi_status(status: bool): | ||||
completer = get_ipython().Completer | ||||
try: | ||||
old = completer.use_jedi | ||||
completer.use_jedi = status | ||||
yield | ||||
finally: | ||||
completer.use_jedi = old | ||||
Matthias Bussonnier
|
r25101 | # ----------------------------------------------------------------------------- | ||
Fernando Perez
|
r2365 | # Test functions | ||
Matthias Bussonnier
|
r25101 | # ----------------------------------------------------------------------------- | ||
Matthias Bussonnier
|
r28561 | |||
Matthias Bussonnier
|
r25729 | def recompute_unicode_ranges(): | ||
""" | ||||
utility to recompute the largest unicode range without any characters | ||||
use to recompute the gap in the global _UNICODE_RANGES of completer.py | ||||
""" | ||||
import itertools | ||||
import unicodedata | ||||
Matthias Bussonnier
|
r28561 | |||
Matthias Bussonnier
|
r25729 | valid = [] | ||
Matthias Bussonnier
|
r28561 | for c in range(0, 0x10FFFF + 1): | ||
Matthias Bussonnier
|
r25729 | try: | ||
unicodedata.name(chr(c)) | ||||
except ValueError: | ||||
continue | ||||
valid.append(c) | ||||
def ranges(i): | ||||
for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]): | ||||
b = list(b) | ||||
yield b[0][1], b[-1][1] | ||||
rg = list(ranges(valid)) | ||||
lens = [] | ||||
gap_lens = [] | ||||
M Bussonnier
|
r28976 | _pstart, pstop = 0, 0 | ||
Matthias Bussonnier
|
r25729 | for start, stop in rg: | ||
Matthias Bussonnier
|
r28561 | lens.append(stop - start) | ||
gap_lens.append( | ||||
( | ||||
start - pstop, | ||||
hex(pstop + 1), | ||||
hex(start), | ||||
f"{round((start - pstop)/0xe01f0*100)}%", | ||||
) | ||||
) | ||||
M Bussonnier
|
r28976 | _pstart, pstop = start, stop | ||
Matthias Bussonnier
|
r25729 | |||
return sorted(gap_lens)[-1] | ||||
Matthias Bussonnier
|
r25711 | def test_unicode_range(): | ||
""" | ||||
Test that the ranges we test for unicode names give the same number of | ||||
results than testing the full length. | ||||
""" | ||||
expected_list = _unicode_name_compute([(0, 0x110000)]) | ||||
test = _unicode_name_compute(_UNICODE_RANGES) | ||||
Matthias Bussonnier
|
r25712 | len_exp = len(expected_list) | ||
len_test = len(test) | ||||
Matthias Bussonnier
|
r25711 | |||
Matthias Bussonnier
|
r25712 | # do not inline the len() or on error pytest will try to print the 130 000 + | ||
# elements. | ||||
Matthias Bussonnier
|
r25729 | message = None | ||
if len_exp != len_test or len_exp > 131808: | ||||
size, start, stop, prct = recompute_unicode_ranges() | ||||
message = f"""_UNICODE_RANGES likely wrong and need updating. This is | ||||
likely due to a new release of Python. We've find that the biggest gap | ||||
Dimitri Papadopoulos
|
r26875 | in unicode characters has reduces in size to be {size} characters | ||
Matthias Bussonnier
|
r25729 | ({prct}), from {start}, to {stop}. In completer.py likely update to | ||
_UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)] | ||||
And update the assertion below to use | ||||
len_exp <= {len_exp} | ||||
""" | ||||
assert len_exp == len_test, message | ||||
Matthias Bussonnier
|
r25712 | |||
Matthias Bussonnier
|
r28561 | # fail if new unicode symbols have been added. | ||
Matthias Bussonnier
|
r28560 | assert len_exp <= 143668, message | ||
Matthias Bussonnier
|
r25711 | |||
MinRK
|
r16564 | |||
@contextmanager | ||||
def greedy_completion(): | ||||
ip = get_ipython() | ||||
greedy_original = ip.Completer.greedy | ||||
try: | ||||
ip.Completer.greedy = True | ||||
yield | ||||
finally: | ||||
ip.Completer.greedy = greedy_original | ||||
Matthias Bussonnier
|
r25101 | |||
krassowski
|
r27775 | @contextmanager | ||
krassowski
|
r27915 | def evaluation_policy(evaluation: str): | ||
krassowski
|
r27906 | ip = get_ipython() | ||
evaluation_original = ip.Completer.evaluation | ||||
try: | ||||
ip.Completer.evaluation = evaluation | ||||
yield | ||||
finally: | ||||
ip.Completer.evaluation = evaluation_original | ||||
@contextmanager | ||||
krassowski
|
r27775 | def custom_matchers(matchers): | ||
ip = get_ipython() | ||||
try: | ||||
ip.Completer.custom_matchers.extend(matchers) | ||||
yield | ||||
finally: | ||||
ip.Completer.custom_matchers.clear() | ||||
M Bussonnier
|
r28976 | if sys.platform == "win32": | ||
pairs = [ | ||||
("abc", "abc"), | ||||
(" abc", '" abc"'), | ||||
("a bc", '"a bc"'), | ||||
("a bc", '"a bc"'), | ||||
(" bc", '" bc"'), | ||||
] | ||||
else: | ||||
pairs = [ | ||||
("abc", "abc"), | ||||
(" abc", r"\ abc"), | ||||
("a bc", r"a\ bc"), | ||||
("a bc", r"a\ \ bc"), | ||||
(" bc", r"\ \ bc"), | ||||
# On posix, we also protect parens and other special characters. | ||||
("a(bc", r"a\(bc"), | ||||
("a)bc", r"a\)bc"), | ||||
("a( )bc", r"a\(\ \)bc"), | ||||
("a[1]bc", r"a\[1\]bc"), | ||||
("a{1}bc", r"a\{1\}bc"), | ||||
("a#bc", r"a\#bc"), | ||||
("a?bc", r"a\?bc"), | ||||
("a=bc", r"a\=bc"), | ||||
("a\\bc", r"a\\bc"), | ||||
("a|bc", r"a\|bc"), | ||||
("a;bc", r"a\;bc"), | ||||
("a:bc", r"a\:bc"), | ||||
("a'bc", r"a\'bc"), | ||||
("a*bc", r"a\*bc"), | ||||
('a"bc', r"a\"bc"), | ||||
("a^bc", r"a\^bc"), | ||||
("a&bc", r"a\&bc"), | ||||
] | ||||
@pytest.mark.parametrize("s1,expected", pairs) | ||||
def test_protect_filename(s1, expected): | ||||
assert completer.protect_filename(s1) == expected | ||||
Fernando Perez
|
r2855 | |||
def check_line_split(splitter, test_specs): | ||||
for part1, part2, split in test_specs: | ||||
cursor_pos = len(part1) | ||||
Matthias Bussonnier
|
r25101 | line = part1 + part2 | ||
Fernando Perez
|
r2855 | out = splitter.split_line(line, cursor_pos) | ||
Samuel Gaist
|
r26888 | assert out == split | ||
Fernando Perez
|
r2855 | |||
def test_line_split(): | ||||
Thomas Kluyver
|
r13375 | """Basic line splitter test with default specs.""" | ||
Fernando Perez
|
r2855 | sp = completer.CompletionSplitter() | ||
# The format of the test specs is: part1, part2, expected answer. Parts 1 | ||||
# and 2 are joined into the 'line' sent to the splitter, as if the cursor | ||||
# was at the end of part1. So an empty part2 represents someone hitting | ||||
# tab at the end of the line, the most common case. | ||||
Matthias Bussonnier
|
r25101 | t = [ | ||
Andrew Kreimer
|
r28888 | ("run some/script", "", "some/script"), | ||
Matthias Bussonnier
|
r25101 | ("run scripts/er", "ror.py foo", "scripts/er"), | ||
("echo $HOM", "", "HOM"), | ||||
("print sys.pa", "", "sys.pa"), | ||||
("print(sys.pa", "", "sys.pa"), | ||||
("execfile('scripts/er", "", "scripts/er"), | ||||
("a[x.", "", "x."), | ||||
("a[x.", "y", "x."), | ||||
('cd "some_file/', "", "some_file/"), | ||||
] | ||||
Fernando Perez
|
r2855 | check_line_split(sp, t) | ||
Fernando Perez
|
r3074 | # Ensure splitting works OK with unicode by re-running the tests with | ||
# all inputs turned into unicode | ||||
Matthias Bussonnier
|
r25101 | check_line_split(sp, [map(str, p) for p in t]) | ||
Piti Ongmongkolkul
|
r6511 | |||
Fernando Perez
|
r3074 | |||
Nikita Kniazev
|
r27127 | class NamedInstanceClass: | ||
instances = {} | ||||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25099 | def __init__(self, name): | ||
Nikita Kniazev
|
r27127 | self.instances[name] = self | ||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25099 | @classmethod | ||
def _ipython_key_completions_(cls): | ||||
return cls.instances.keys() | ||||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25100 | class KeyCompletable: | ||
Matthias Bussonnier
|
r25099 | def __init__(self, things=()): | ||
self.things = things | ||||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25099 | def _ipython_key_completions_(self): | ||
return list(self.things) | ||||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25099 | class TestCompleter(unittest.TestCase): | ||
Fernando Perez
|
r2855 | def setUp(self): | ||
Matthias Bussonnier
|
r25099 | """ | ||
We want to silence all PendingDeprecationWarning when testing the completer | ||||
""" | ||||
self._assertwarns = self.assertWarns(PendingDeprecationWarning) | ||||
self._assertwarns.__enter__() | ||||
def tearDown(self): | ||||
try: | ||||
self._assertwarns.__exit__(None, None, None) | ||||
except AssertionError: | ||||
pass | ||||
def test_custom_completion_error(self): | ||||
"""Test that errors from custom attribute completers are silenced.""" | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | |||
class A: | ||||
pass | ||||
ip.user_ns["x"] = A() | ||||
Matthias Bussonnier
|
r25099 | @complete_object.register(A) | ||
def complete_A(a, existing_completions): | ||||
raise TypeError("this should be silenced") | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip.complete("x.") | ||
Tony Fast
|
r25168 | def test_custom_completion_ordering(self): | ||
"""Test that errors from custom attribute completers are silenced.""" | ||||
ip = get_ipython() | ||||
_, matches = ip.complete('in') | ||||
assert matches.index('input') < matches.index('int') | ||||
def complete_example(a): | ||||
return ['example2', 'example1'] | ||||
ip.Completer.custom_completers.add_re('ex*', complete_example) | ||||
_, matches = ip.complete('ex') | ||||
assert matches.index('example2') < matches.index('example1') | ||||
Matthias Bussonnier
|
r25099 | def test_unicode_completions(self): | ||
ip = get_ipython() | ||||
# Some strings that trigger different types of completion. Check them both | ||||
# in str and unicode forms | ||||
Matthias Bussonnier
|
r25101 | s = ["ru", "%ru", "cd /", "floa", "float(x)/"] | ||
Matthias Bussonnier
|
r25099 | for t in s + list(map(str, s)): | ||
# We don't need to check exact completion values (they may change | ||||
# depending on the state of the namespace, but at least no exceptions | ||||
# should be thrown and the return value should be a pair of text, list | ||||
# values. | ||||
text, matches = ip.complete(t) | ||||
Samuel Gaist
|
r26888 | self.assertIsInstance(text, str) | ||
self.assertIsInstance(matches, list) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_latex_completions(self): | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip = get_ipython() | ||
# Test some random unicode symbols | ||||
Nikita Kniazev
|
r27105 | keys = random.sample(sorted(latex_symbols), 10) | ||
Matthias Bussonnier
|
r25099 | for k in keys: | ||
text, matches = ip.complete(k) | ||||
Samuel Gaist
|
r26888 | self.assertEqual(text, k) | ||
self.assertEqual(matches, [latex_symbols[k]]) | ||||
Matthias Bussonnier
|
r25099 | # Test a more complex line | ||
Matthias Bussonnier
|
r25101 | text, matches = ip.complete("print(\\alpha") | ||
Samuel Gaist
|
r26888 | self.assertEqual(text, "\\alpha") | ||
self.assertEqual(matches[0], latex_symbols["\\alpha"]) | ||||
Matthias Bussonnier
|
r25099 | # Test multiple matching latex symbols | ||
Matthias Bussonnier
|
r25101 | text, matches = ip.complete("\\al") | ||
Samuel Gaist
|
r26888 | self.assertIn("\\alpha", matches) | ||
self.assertIn("\\aleph", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r25686 | def test_latex_no_results(self): | ||
""" | ||||
forward latex should really return nothing in either field if nothing is found. | ||||
""" | ||||
ip = get_ipython() | ||||
text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(text, "") | ||
self.assertEqual(matches, ()) | ||||
Matthias Bussonnier
|
r25686 | |||
Matthias Bussonnier
|
r25099 | def test_back_latex_completion(self): | ||
ip = get_ipython() | ||||
Dimitri Papadopoulos
|
r26875 | # do not return more than 1 matches for \beta, only the latex one. | ||
Matthias Bussonnier
|
r25101 | name, matches = ip.complete("\\β") | ||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["\\beta"]) | ||
Matthias Bussonnier
|
r25099 | |||
def test_back_unicode_completion(self): | ||||
ip = get_ipython() | ||||
Thomas Kluyver
|
r16767 | |||
Matthias Bussonnier
|
r25101 | name, matches = ip.complete("\\â…¤") | ||
krassowski
|
r27768 | self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"]) | ||
Thomas Kluyver
|
r16767 | |||
Matthias Bussonnier
|
r25099 | def test_forward_unicode_completion(self): | ||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | |||
name, matches = ip.complete("\\ROMAN NUMERAL FIVE") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["â…¤"]) # This is not a V | ||
self.assertEqual(matches, ["\u2164"]) # same as above but explicit. | ||||
Matthias Bussonnier
|
r25099 | |||
Nikita Kniazev
|
r26989 | def test_delim_setting(self): | ||
sp = completer.CompletionSplitter() | ||||
sp.delims = " " | ||||
self.assertEqual(sp.delims, " ") | ||||
self.assertEqual(sp._delim_expr, r"[\ ]") | ||||
def test_spaces(self): | ||||
"""Test with only spaces as split chars.""" | ||||
sp = completer.CompletionSplitter() | ||||
sp.delims = " " | ||||
t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")] | ||||
check_line_split(sp, t) | ||||
MinRK
|
r4825 | |||
Matthias Bussonnier
|
r25099 | def test_has_open_quotes1(self): | ||
for s in ["'", "'''", "'hi' '"]: | ||||
Samuel Gaist
|
r26888 | self.assertEqual(completer.has_open_quotes(s), "'") | ||
Matthias Bussonnier
|
r25099 | |||
def test_has_open_quotes2(self): | ||||
for s in ['"', '"""', '"hi" "']: | ||||
Samuel Gaist
|
r26888 | self.assertEqual(completer.has_open_quotes(s), '"') | ||
Matthias Bussonnier
|
r25099 | |||
def test_has_open_quotes3(self): | ||||
for s in ["''", "''' '''", "'hi' 'ipython'"]: | ||||
Samuel Gaist
|
r26888 | self.assertFalse(completer.has_open_quotes(s)) | ||
Matthias Bussonnier
|
r25099 | |||
def test_has_open_quotes4(self): | ||||
for s in ['""', '""" """', '"hi" "ipython"']: | ||||
Samuel Gaist
|
r26888 | self.assertFalse(completer.has_open_quotes(s)) | ||
Matthias Bussonnier
|
r25099 | |||
Nikita Kniazev
|
r27042 | @pytest.mark.xfail( | ||
sys.platform == "win32", reason="abspath completions fail on Windows" | ||||
Matthias Bussonnier
|
r25101 | ) | ||
Matthias Bussonnier
|
r25099 | def test_abspath_file_completions(self): | ||
ip = get_ipython() | ||||
with TemporaryDirectory() as tmpdir: | ||||
Matthias Bussonnier
|
r25101 | prefix = os.path.join(tmpdir, "foo") | ||
suffixes = ["1", "2"] | ||||
names = [prefix + s for s in suffixes] | ||||
Matthias Bussonnier
|
r25099 | for n in names: | ||
gousaiyang
|
r27495 | open(n, "w", encoding="utf-8").close() | ||
Matthias Bussonnier
|
r25099 | |||
# Check simple completion | ||||
c = ip.complete(prefix)[1] | ||||
Samuel Gaist
|
r26888 | self.assertEqual(c, names) | ||
Matthias Bussonnier
|
r25099 | |||
# Now check with a function call | ||||
cmd = 'a = f("%s' % prefix | ||||
c = ip.complete(prefix, cmd)[1] | ||||
Matthias Bussonnier
|
r25101 | comp = [prefix + s for s in suffixes] | ||
Samuel Gaist
|
r26888 | self.assertEqual(c, comp) | ||
Matthias Bussonnier
|
r25099 | |||
def test_local_file_completions(self): | ||||
ip = get_ipython() | ||||
with TemporaryWorkingDirectory(): | ||||
Matthias Bussonnier
|
r25101 | prefix = "./foo" | ||
suffixes = ["1", "2"] | ||||
names = [prefix + s for s in suffixes] | ||||
Matthias Bussonnier
|
r25099 | for n in names: | ||
gousaiyang
|
r27495 | open(n, "w", encoding="utf-8").close() | ||
Matthias Bussonnier
|
r25099 | |||
# Check simple completion | ||||
c = ip.complete(prefix)[1] | ||||
Samuel Gaist
|
r26888 | self.assertEqual(c, names) | ||
Matthias Bussonnier
|
r25099 | |||
# Now check with a function call | ||||
cmd = 'a = f("%s' % prefix | ||||
c = ip.complete(prefix, cmd)[1] | ||||
Matthias Bussonnier
|
r25101 | comp = {prefix + s for s in suffixes} | ||
Samuel Gaist
|
r26888 | self.assertTrue(comp.issubset(set(c))) | ||
Matthias Bussonnier
|
r25099 | |||
def test_quoted_file_completions(self): | ||||
ip = get_ipython() | ||||
krassowski
|
r27768 | |||
def _(text): | ||||
return ip.Completer._complete( | ||||
cursor_line=0, cursor_pos=len(text), full_text=text | ||||
)["IPCompleter.file_matcher"]["completions"] | ||||
Matthias Bussonnier
|
r25099 | with TemporaryWorkingDirectory(): | ||
name = "foo'bar" | ||||
gousaiyang
|
r27495 | open(name, "w", encoding="utf-8").close() | ||
Matthias Bussonnier
|
r25099 | |||
# Don't escape Windows | ||||
escaped = name if sys.platform == "win32" else "foo\\'bar" | ||||
# Single quote matches embedded single quote | ||||
krassowski
|
r27768 | c = _("open('foo")[0] | ||
self.assertEqual(c.text, escaped) | ||||
Matthias Bussonnier
|
r25099 | |||
# Double quote requires no escape | ||||
krassowski
|
r27768 | c = _('open("foo')[0] | ||
self.assertEqual(c.text, name) | ||||
Matthias Bussonnier
|
r25099 | |||
# No quote requires an escape | ||||
krassowski
|
r27768 | c = _("%ls foo")[0] | ||
self.assertEqual(c.text, escaped) | ||||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
Brandon T. Willard
|
r25122 | def test_all_completions_dups(self): | ||
""" | ||||
Make sure the output of `IPCompleter.all_completions` does not have | ||||
duplicated prefixes. | ||||
""" | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
ip.ex("class TestClass():\n\ta=1\n\ta1=2") | ||||
for jedi_status in [True, False]: | ||||
with provisionalcompleter(): | ||||
ip.Completer.use_jedi = jedi_status | ||||
matches = c.all_completions("TestCl") | ||||
Matthias Bussonnier
|
r27356 | assert matches == ["TestClass"], (jedi_status, matches) | ||
Brandon T. Willard
|
r25122 | matches = c.all_completions("TestClass.") | ||
Matthias Bussonnier
|
r27356 | assert len(matches) > 2, (jedi_status, matches) | ||
Brandon T. Willard
|
r25122 | matches = c.all_completions("TestClass.a") | ||
krassowski
|
r28808 | if jedi_status: | ||
assert matches == ["TestClass.a", "TestClass.a1"], jedi_status | ||||
else: | ||||
assert matches == [".a", ".a1"], jedi_status | ||||
Brandon T. Willard
|
r25122 | |||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
Matthias Bussonnier
|
r25099 | def test_jedi(self): | ||
""" | ||||
A couple of issue we had with Jedi | ||||
""" | ||||
ip = get_ipython() | ||||
def _test_complete(reason, s, comp, start=None, end=None): | ||||
l = len(s) | ||||
start = start if start is not None else l | ||||
end = end if end is not None else l | ||||
with provisionalcompleter(): | ||||
ip.Completer.use_jedi = True | ||||
completions = set(ip.Completer.completions(s, l)) | ||||
ip.Completer.use_jedi = False | ||||
Nikita Kniazev
|
r26989 | assert Completion(start, end, comp) in completions, reason | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | def _test_not_complete(reason, s, comp): | ||
l = len(s) | ||||
with provisionalcompleter(): | ||||
ip.Completer.use_jedi = True | ||||
completions = set(ip.Completer.completions(s, l)) | ||||
ip.Completer.use_jedi = False | ||||
Nikita Kniazev
|
r26989 | assert Completion(l, l, comp) not in completions, reason | ||
Matthias Bussonnier
|
r25099 | |||
import jedi | ||||
Matthias Bussonnier
|
r25101 | |||
jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3]) | ||||
Matthias Bussonnier
|
r25099 | if jedi_version > (0, 10): | ||
Nikita Kniazev
|
r26989 | _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real") | ||
_test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real") | ||||
_test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize") | ||||
_test_complete("cover duplicate completions", "im", "import", 0, 2) | ||||
Matthias Bussonnier
|
r25099 | |||
Nikita Kniazev
|
r26989 | _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize") | ||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
Matthias Bussonnier
|
r25099 | def test_completion_have_signature(self): | ||
""" | ||||
Lets make sure jedi is capable of pulling out the signature of the function we are completing. | ||||
""" | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r23284 | with provisionalcompleter(): | ||
Thomas Kluyver
|
r24281 | ip.Completer.use_jedi = True | ||
Matthias Bussonnier
|
r25101 | completions = ip.Completer.completions("ope", 3) | ||
Matthias Bussonnier
|
r25099 | c = next(completions) # should be `open` | ||
Thomas Kluyver
|
r24281 | ip.Completer.use_jedi = False | ||
Matthias Bussonnier
|
r25101 | assert "file" in c.signature, "Signature of function was not found by completer" | ||
assert ( | ||||
"encoding" in c.signature | ||||
), "Signature of function was not found by completer" | ||||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
krassowski
|
r27768 | def test_completions_have_type(self): | ||
""" | ||||
Lets make sure matchers provide completion type. | ||||
""" | ||||
ip = get_ipython() | ||||
with provisionalcompleter(): | ||||
ip.Completer.use_jedi = False | ||||
completions = ip.Completer.completions("%tim", 3) | ||||
c = next(completions) # should be `%time` or similar | ||||
assert c.type == "magic", "Type of magic was not assigned by completer" | ||||
M Bussonnier
|
r28800 | @pytest.mark.xfail( | ||
M Bussonnier
|
r28801 | parse(version("jedi")) <= parse("0.18.0"), | ||
reason="Known failure on jedi<=0.18.0", | ||||
strict=True, | ||||
M Bussonnier
|
r28800 | ) | ||
Matthias Bussonnier
|
r25099 | def test_deduplicate_completions(self): | ||
""" | ||||
Test that completions are correctly deduplicated (even if ranges are not the same) | ||||
""" | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | ip.ex( | ||
textwrap.dedent( | ||||
""" | ||||
Matthias Bussonnier
|
r25099 | class Z: | ||
zoo = 1 | ||||
Matthias Bussonnier
|
r25101 | """ | ||
) | ||||
) | ||||
Matthias Bussonnier
|
r23284 | with provisionalcompleter(): | ||
Thomas Kluyver
|
r24281 | ip.Completer.use_jedi = True | ||
Matthias Bussonnier
|
r25101 | l = list( | ||
_deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3)) | ||||
) | ||||
Thomas Kluyver
|
r24281 | ip.Completer.use_jedi = False | ||
Matthias Bussonnier
|
r23753 | |||
Matthias Bussonnier
|
r25101 | assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l | ||
assert l[0].text == "zoo" # and not `it.accumulate` | ||||
Matthias Bussonnier
|
r23358 | |||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
Matthias Bussonnier
|
r25099 | def test_greedy_completions(self): | ||
""" | ||||
krassowski
|
r27906 | Test the capability of the Greedy completer. | ||
Matthias Bussonnier
|
r23358 | |||
Matthias Bussonnier
|
r25099 | Most of the test here does not really show off the greedy completer, for proof | ||
krassowski
|
r27906 | each of the text below now pass with Jedi. The greedy completer is capable of more. | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | See the :any:`test_dict_key_completion_contexts` | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | """ | ||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | ip.ex("a=list(range(5))") | ||
krassowski
|
r28112 | ip.ex("d = {'a b': str}") | ||
Matthias Bussonnier
|
r25101 | _, c = ip.complete(".", line="a[0].") | ||
Samuel Gaist
|
r26888 | self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c) | ||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | def _(line, cursor_pos, expect, message, completion): | ||
with greedy_completion(), provisionalcompleter(): | ||||
ip.Completer.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | _, c = ip.complete(".", line=line, cursor_pos=cursor_pos) | ||
Samuel Gaist
|
r26888 | self.assertIn(expect, c, message % c) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | ip.Completer.use_jedi = True | ||
with provisionalcompleter(): | ||||
completions = ip.Completer.completions(line, cursor_pos) | ||||
krassowski
|
r28808 | self.assertIn(completion, list(completions)) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | with provisionalcompleter(): | ||
Nikita Kniazev
|
r26989 | _( | ||
"a[0].", | ||||
5, | ||||
krassowski
|
r28112 | ".real", | ||
Nikita Kniazev
|
r26989 | "Should have completed on a[0].: %s", | ||
Completion(5, 5, "real"), | ||||
Matthias Bussonnier
|
r25101 | ) | ||
Nikita Kniazev
|
r26989 | _( | ||
"a[0].r", | ||||
6, | ||||
krassowski
|
r28112 | ".real", | ||
Nikita Kniazev
|
r26989 | "Should have completed on a[0].r: %s", | ||
Completion(5, 6, "real"), | ||||
Matthias Bussonnier
|
r25101 | ) | ||
Matthias Bussonnier
|
r25099 | |||
Nikita Kniazev
|
r26989 | _( | ||
"a[0].from_", | ||||
10, | ||||
krassowski
|
r28112 | ".from_bytes", | ||
Nikita Kniazev
|
r26989 | "Should have completed on a[0].from_: %s", | ||
Completion(5, 10, "from_bytes"), | ||||
kousik
|
r25246 | ) | ||
krassowski
|
r28112 | _( | ||
"assert str.star", | ||||
14, | ||||
krassowski
|
r28808 | ".startswith", | ||
krassowski
|
r28112 | "Should have completed on `assert str.star`: %s", | ||
Completion(11, 14, "startswith"), | ||||
) | ||||
_( | ||||
"d['a b'].str", | ||||
12, | ||||
".strip", | ||||
"Should have completed on `d['a b'].str`: %s", | ||||
Completion(9, 12, "strip"), | ||||
) | ||||
krassowski
|
r28808 | _( | ||
"a.app", | ||||
4, | ||||
".append", | ||||
"Should have completed on `a.app`: %s", | ||||
Completion(2, 4, "append"), | ||||
) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_omit__names(self): | ||||
# also happens to test IPCompleter as a configurable | ||||
ip = get_ipython() | ||||
ip._hidden_attr = 1 | ||||
ip._x = {} | ||||
c = ip.Completer | ||||
Matthias Bussonnier
|
r25101 | ip.ex("ip=get_ipython()") | ||
Matthias Bussonnier
|
r25099 | cfg = Config() | ||
cfg.IPCompleter.omit__names = 0 | ||||
c.update_config(cfg) | ||||
with provisionalcompleter(): | ||||
c.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete("ip.") | ||
krassowski
|
r28808 | self.assertIn(".__str__", matches) | ||
self.assertIn("._hidden_attr", matches) | ||||
Kelly Liu
|
r22292 | |||
Matthias Bussonnier
|
r25099 | # c.use_jedi = True | ||
# completions = set(c.completions('ip.', 3)) | ||||
Samuel Gaist
|
r26888 | # self.assertIn(Completion(3, 3, '__str__'), completions) | ||
# self.assertIn(Completion(3,3, "_hidden_attr"), completions) | ||||
Kelly Liu
|
r22292 | |||
Matthias Bussonnier
|
r25099 | cfg = Config() | ||
cfg.IPCompleter.omit__names = 1 | ||||
c.update_config(cfg) | ||||
with provisionalcompleter(): | ||||
c.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete("ip.") | ||
krassowski
|
r28808 | self.assertNotIn(".__str__", matches) | ||
Samuel Gaist
|
r26888 | # self.assertIn('ip._hidden_attr', matches) | ||
Matthias Bussonnier
|
r25099 | |||
# c.use_jedi = True | ||||
# completions = set(c.completions('ip.', 3)) | ||||
Samuel Gaist
|
r26888 | # self.assertNotIn(Completion(3,3,'__str__'), completions) | ||
# self.assertIn(Completion(3,3, "_hidden_attr"), completions) | ||||
Matthias Bussonnier
|
r25099 | |||
cfg = Config() | ||||
cfg.IPCompleter.omit__names = 2 | ||||
c.update_config(cfg) | ||||
with provisionalcompleter(): | ||||
c.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete("ip.") | ||
krassowski
|
r28808 | self.assertNotIn(".__str__", matches) | ||
self.assertNotIn("._hidden_attr", matches) | ||||
Piti Ongmongkolkul
|
r6511 | |||
Matthias Bussonnier
|
r25099 | # c.use_jedi = True | ||
# completions = set(c.completions('ip.', 3)) | ||||
Samuel Gaist
|
r26888 | # self.assertNotIn(Completion(3,3,'__str__'), completions) | ||
# self.assertNotIn(Completion(3,3, "_hidden_attr"), completions) | ||||
Thomas Kluyver
|
r24128 | |||
Matthias Bussonnier
|
r25099 | with provisionalcompleter(): | ||
c.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete("ip._x.") | ||
krassowski
|
r28808 | self.assertIn(".keys", matches) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | # c.use_jedi = True | ||
# completions = set(c.completions('ip._x.', 6)) | ||||
Samuel Gaist
|
r26888 | # self.assertIn(Completion(6,6, "keys"), completions) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | del ip._hidden_attr | ||
del ip._x | ||||
Thomas Kluyver
|
r24128 | |||
Matthias Bussonnier
|
r25099 | def test_limit_to__all__False_ok(self): | ||
""" | ||||
krassowski
|
r28808 | Limit to all is deprecated, once we remove it this test can go away. | ||
Matthias Bussonnier
|
r25099 | """ | ||
ip = get_ipython() | ||||
c = ip.Completer | ||||
Thomas Kluyver
|
r24128 | c.use_jedi = False | ||
Matthias Bussonnier
|
r25101 | ip.ex("class D: x=24") | ||
ip.ex("d=D()") | ||||
Matthias Bussonnier
|
r25099 | cfg = Config() | ||
cfg.IPCompleter.limit_to__all__ = False | ||||
c.update_config(cfg) | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete("d.") | ||
krassowski
|
r28808 | self.assertIn(".x", matches) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25099 | def test_get__all__entries_ok(self): | ||
Matthias Bussonnier
|
r25100 | class A: | ||
Matthias Bussonnier
|
r25101 | __all__ = ["x", 1] | ||
Tim Couper
|
r6308 | |||
Matthias Bussonnier
|
r25101 | words = completer.get__all__entries(A()) | ||
Samuel Gaist
|
r26888 | self.assertEqual(words, ["x"]) | ||
Tim Couper
|
r6312 | |||
Matthias Bussonnier
|
r25099 | def test_get__all__entries_no__all__ok(self): | ||
Matthias Bussonnier
|
r25100 | class A: | ||
Matthias Bussonnier
|
r25099 | pass | ||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | words = completer.get__all__entries(A()) | ||
Samuel Gaist
|
r26888 | self.assertEqual(words, []) | ||
Tim Couper
|
r6312 | |||
Matthias Bussonnier
|
r25099 | def test_func_kw_completions(self): | ||
ip = get_ipython() | ||||
c = ip.Completer | ||||
c.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | ip.ex("def myfunc(a=1,b=2): return a+b") | ||
s, matches = c.complete(None, "myfunc(1,b") | ||||
Samuel Gaist
|
r26888 | self.assertIn("b=", matches) | ||
Matthias Bussonnier
|
r25099 | # Simulate completing with cursor right after b (pos==10): | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "myfunc(1,b)", 10) | ||
Samuel Gaist
|
r26888 | self.assertIn("b=", matches) | ||
Matthias Bussonnier
|
r25099 | s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') | ||
Samuel Gaist
|
r26888 | self.assertIn("b=", matches) | ||
Matthias Bussonnier
|
r25101 | # builtin function | ||
s, matches = c.complete(None, "min(k, k") | ||||
Samuel Gaist
|
r26888 | self.assertIn("key=", matches) | ||
Matthias Bussonnier
|
r25099 | |||
def test_default_arguments_from_docstring(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
Matthias Bussonnier
|
r25101 | kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value") | ||
Samuel Gaist
|
r26888 | self.assertEqual(kwd, ["key"]) | ||
Matthias Bussonnier
|
r25101 | # with cython type etc | ||
Matthias Bussonnier
|
r25099 | kwd = c._default_arguments_from_docstring( | ||
Matthias Bussonnier
|
r25101 | "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" | ||
) | ||||
Samuel Gaist
|
r26888 | self.assertEqual(kwd, ["ncall", "resume", "nsplit"]) | ||
Matthias Bussonnier
|
r25101 | # white spaces | ||
Matthias Bussonnier
|
r25099 | kwd = c._default_arguments_from_docstring( | ||
Matthias Bussonnier
|
r25101 | "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" | ||
) | ||||
Samuel Gaist
|
r26888 | self.assertEqual(kwd, ["ncall", "resume", "nsplit"]) | ||
Matthias Bussonnier
|
r25099 | |||
def test_line_magics(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "lsmag") | ||
Samuel Gaist
|
r26888 | self.assertIn("%lsmagic", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%lsmag") | ||
Samuel Gaist
|
r26888 | self.assertIn("%lsmagic", matches) | ||
Matthias Bussonnier
|
r25099 | |||
def test_cell_magics(self): | ||||
from IPython.core.magic import register_cell_magic | ||||
@register_cell_magic | ||||
def _foo_cellm(line, cell): | ||||
pass | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip = get_ipython() | ||
c = ip.Completer | ||||
Piti Ongmongkolkul
|
r6511 | |||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "_foo_ce") | ||
Samuel Gaist
|
r26888 | self.assertIn("%%_foo_cellm", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%%_foo_ce") | ||
Samuel Gaist
|
r26888 | self.assertIn("%%_foo_cellm", matches) | ||
Piti Ongmongkolkul
|
r6511 | |||
Matthias Bussonnier
|
r25099 | def test_line_cell_magics(self): | ||
from IPython.core.magic import register_line_cell_magic | ||||
Piti Ongmongkolkul
|
r6511 | |||
Matthias Bussonnier
|
r25099 | @register_line_cell_magic | ||
def _bar_cellm(line, cell): | ||||
pass | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip = get_ipython() | ||
c = ip.Completer | ||||
# The policy here is trickier, see comments in completion code. The | ||||
# returned values depend on whether the user passes %% or not explicitly, | ||||
# and this will show a difference if the same name is both a line and cell | ||||
# magic. | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "_bar_ce") | ||
Samuel Gaist
|
r26888 | self.assertIn("%_bar_cellm", matches) | ||
self.assertIn("%%_bar_cellm", matches) | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%_bar_ce") | ||
Samuel Gaist
|
r26888 | self.assertIn("%_bar_cellm", matches) | ||
self.assertIn("%%_bar_cellm", matches) | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%%_bar_ce") | ||
Samuel Gaist
|
r26888 | self.assertNotIn("%_bar_cellm", matches) | ||
self.assertIn("%%_bar_cellm", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_magic_completion_order(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
# Test ordering of line and cell magics. | ||||
text, matches = c.complete("timeit") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["%timeit", "%%timeit"]) | ||
Matthias Bussonnier
|
r25099 | |||
def test_magic_completion_shadowing(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
c.use_jedi = False | ||||
Fernando Perez
|
r6991 | |||
Matthias Bussonnier
|
r25099 | # Before importing matplotlib, %matplotlib magic should be the only option. | ||
text, matches = c.complete("mat") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["%matplotlib"]) | ||
Matthias Bussonnier
|
r25099 | |||
# The newly introduced name should shadow the magic. | ||||
ip.run_cell("matplotlib = 1") | ||||
text, matches = c.complete("mat") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["matplotlib"]) | ||
Matthias Bussonnier
|
r25099 | |||
# After removing matplotlib from namespace, the magic should again be | ||||
# the only option. | ||||
del ip.user_ns["matplotlib"] | ||||
text, matches = c.complete("mat") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["%matplotlib"]) | ||
Matthias Bussonnier
|
r25099 | |||
def test_magic_completion_shadowing_explicit(self): | ||||
""" | ||||
If the user try to complete a shadowed magic, and explicit % start should | ||||
still return the completions. | ||||
""" | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
# Before importing matplotlib, %matplotlib magic should be the only option. | ||||
text, matches = c.complete("%mat") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["%matplotlib"]) | ||
Matthias Bussonnier
|
r25099 | |||
ip.run_cell("matplotlib = 1") | ||||
# After removing matplotlib from namespace, the magic should still be | ||||
# the only option. | ||||
text, matches = c.complete("%mat") | ||||
Samuel Gaist
|
r26888 | self.assertEqual(matches, ["%matplotlib"]) | ||
Matthias Bussonnier
|
r25099 | |||
def test_magic_config(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "conf") | ||
Samuel Gaist
|
r26888 | self.assertIn("%config", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "conf") | ||
Samuel Gaist
|
r26888 | self.assertNotIn("AliasManager", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config ") | ||
Samuel Gaist
|
r26888 | self.assertIn("AliasManager", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%config ") | ||
Samuel Gaist
|
r26888 | self.assertIn("AliasManager", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config Ali") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%config Ali") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config AliasManager") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%config AliasManager") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config AliasManager.") | ||
Samuel Gaist
|
r26888 | self.assertIn("AliasManager.default_aliases", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%config AliasManager.") | ||
Samuel Gaist
|
r26888 | self.assertIn("AliasManager.default_aliases", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config AliasManager.de") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager.default_aliases"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "config AliasManager.de") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["AliasManager.default_aliases"], matches) | ||
Matthias Bussonnier
|
r25099 | |||
def test_magic_color(self): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "colo") | ||
Samuel Gaist
|
r26888 | self.assertIn("%colors", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "colo") | ||
Samuel Gaist
|
r26888 | self.assertNotIn("NoColor", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%colors") # No trailing space | ||
Samuel Gaist
|
r26888 | self.assertNotIn("NoColor", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "colors ") | ||
Samuel Gaist
|
r26888 | self.assertIn("NoColor", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%colors ") | ||
Samuel Gaist
|
r26888 | self.assertIn("NoColor", matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "colors NoCo") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["NoColor"], matches) | ||
Matthias Bussonnier
|
r25101 | s, matches = c.complete(None, "%colors NoCo") | ||
Samuel Gaist
|
r26888 | self.assertListEqual(["NoColor"], matches) | ||
Matthias Bussonnier
|
r25099 | |||
def test_match_dict_keys(self): | ||||
""" | ||||
Test that match_dict_keys works on a couple of use case does return what | ||||
expected, and does not crash | ||||
""" | ||||
Matthias Bussonnier
|
r25101 | delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?" | ||
Matthias Bussonnier
|
r25099 | |||
krassowski
|
r27912 | def match(*args, **kwargs): | ||
krassowski
|
r27916 | quote, offset, matches = match_dict_keys(*args, delims=delims, **kwargs) | ||
krassowski
|
r27912 | return quote, offset, list(matches) | ||
Matthias Bussonnier
|
r25101 | keys = ["foo", b"far"] | ||
krassowski
|
r27916 | assert match(keys, "b'") == ("'", 2, ["far"]) | ||
assert match(keys, "b'f") == ("'", 2, ["far"]) | ||||
assert match(keys, 'b"') == ('"', 2, ["far"]) | ||||
assert match(keys, 'b"f') == ('"', 2, ["far"]) | ||||
Matthias Bussonnier
|
r25099 | |||
krassowski
|
r27916 | assert match(keys, "'") == ("'", 1, ["foo"]) | ||
assert match(keys, "'f") == ("'", 1, ["foo"]) | ||||
assert match(keys, '"') == ('"', 1, ["foo"]) | ||||
assert match(keys, '"f') == ('"', 1, ["foo"]) | ||||
krassowski
|
r27912 | |||
# Completion on first item of tuple | ||||
krassowski
|
r27913 | keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, "test")] | ||
krassowski
|
r27916 | assert match(keys, "'f") == ("'", 1, ["foo"]) | ||
assert match(keys, "33") == ("", 0, ["3333"]) | ||||
krassowski
|
r27912 | |||
# Completion on numbers | ||||
krassowski
|
r27916 | keys = [ | ||
0xDEADBEEF, | ||||
1111, | ||||
1234, | ||||
"1999", | ||||
0b10101, | ||||
22, | ||||
] # 0xDEADBEEF = 3735928559; 0b10101 = 21 | ||||
assert match(keys, "0xdead") == ("", 0, ["0xdeadbeef"]) | ||||
assert match(keys, "1") == ("", 0, ["1111", "1234"]) | ||||
assert match(keys, "2") == ("", 0, ["21", "22"]) | ||||
assert match(keys, "0b101") == ("", 0, ["0b10101", "0b10110"]) | ||||
Matthias Bussonnier
|
r25099 | |||
krassowski
|
r27927 | # Should yield on variables | ||
assert match(keys, "a_variable") == ("", 0, []) | ||||
# Should pass over invalid literals | ||||
assert match(keys, "'' ''") == ("", 0, []) | ||||
Corentin Cadiou
|
r25851 | def test_match_dict_keys_tuple(self): | ||
""" | ||||
Test that match_dict_keys called with extra prefix works on a couple of use case, | ||||
does return what expected, and does not crash. | ||||
""" | ||||
delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?" | ||||
krassowski
|
r27906 | |||
Corentin Cadiou
|
r25851 | keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')] | ||
krassowski
|
r27916 | def match(*args, extra=None, **kwargs): | ||
quote, offset, matches = match_dict_keys( | ||||
*args, delims=delims, extra_prefix=extra, **kwargs | ||||
) | ||||
krassowski
|
r27912 | return quote, offset, list(matches) | ||
Corentin Cadiou
|
r25851 | # Completion on first key == "foo" | ||
krassowski
|
r27916 | assert match(keys, "'", extra=("foo",)) == ("'", 1, ["bar", "oof"]) | ||
assert match(keys, '"', extra=("foo",)) == ('"', 1, ["bar", "oof"]) | ||||
assert match(keys, "'o", extra=("foo",)) == ("'", 1, ["oof"]) | ||||
assert match(keys, '"o', extra=("foo",)) == ('"', 1, ["oof"]) | ||||
assert match(keys, "b'", extra=("foo",)) == ("'", 2, ["bar"]) | ||||
assert match(keys, 'b"', extra=("foo",)) == ('"', 2, ["bar"]) | ||||
assert match(keys, "b'b", extra=("foo",)) == ("'", 2, ["bar"]) | ||||
assert match(keys, 'b"b', extra=("foo",)) == ('"', 2, ["bar"]) | ||||
Corentin Cadiou
|
r25851 | |||
# No Completion | ||||
krassowski
|
r27916 | assert match(keys, "'", extra=("no_foo",)) == ("'", 1, []) | ||
assert match(keys, "'", extra=("fo",)) == ("'", 1, []) | ||||
Corentin Cadiou
|
r25851 | |||
krassowski
|
r27913 | keys = [("foo1", "foo2", "foo3", "foo4"), ("foo1", "foo2", "bar", "foo4")] | ||
krassowski
|
r27916 | assert match(keys, "'foo", extra=("foo1",)) == ("'", 1, ["foo2"]) | ||
assert match(keys, "'foo", extra=("foo1", "foo2")) == ("'", 1, ["foo3"]) | ||||
assert match(keys, "'foo", extra=("foo1", "foo2", "foo3")) == ("'", 1, ["foo4"]) | ||||
assert match(keys, "'foo", extra=("foo1", "foo2", "foo3", "foo4")) == ( | ||||
krassowski
|
r27913 | "'", | ||
1, | ||||
krassowski
|
r27916 | [], | ||
krassowski
|
r27913 | ) | ||
krassowski
|
r27912 | |||
keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)] | ||||
krassowski
|
r27916 | assert match(keys, "'", extra=("foo",)) == ("'", 1, ["2222"]) | ||
assert match(keys, "", extra=("foo",)) == ("", 0, ["1111", "'2222'"]) | ||||
assert match(keys, "'", extra=(3333,)) == ("'", 1, ["bar"]) | ||||
assert match(keys, "", extra=(3333,)) == ("", 0, ["'bar'", "4444"]) | ||||
assert match(keys, "'", extra=("3333",)) == ("'", 1, []) | ||||
assert match(keys, "33") == ("", 0, ["3333"]) | ||||
krassowski
|
r27912 | |||
def test_dict_key_completion_closures(self): | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
ip.Completer.auto_close_dict_keys = True | ||||
Corentin Cadiou
|
r25851 | |||
krassowski
|
r27912 | ip.user_ns["d"] = { | ||
# tuple only | ||||
krassowski
|
r27913 | ("aa", 11): None, | ||
krassowski
|
r27912 | # tuple and non-tuple | ||
krassowski
|
r27913 | ("bb", 22): None, | ||
"bb": None, | ||||
krassowski
|
r27912 | # non-tuple only | ||
krassowski
|
r27913 | "cc": None, | ||
krassowski
|
r27912 | # numeric tuple only | ||
krassowski
|
r27913 | (77, "x"): None, | ||
krassowski
|
r27912 | # numeric tuple and non-tuple | ||
krassowski
|
r27913 | (88, "y"): None, | ||
krassowski
|
r27912 | 88: None, | ||
# numeric non-tuple only | ||||
99: None, | ||||
} | ||||
_, matches = complete(line_buffer="d[") | ||||
# should append `, ` if matches a tuple only | ||||
self.assertIn("'aa', ", matches) | ||||
# should not append anything if matches a tuple and an item | ||||
self.assertIn("'bb'", matches) | ||||
# should append `]` if matches and item only | ||||
self.assertIn("'cc']", matches) | ||||
# should append `, ` if matches a tuple only | ||||
self.assertIn("77, ", matches) | ||||
# should not append anything if matches a tuple and an item | ||||
self.assertIn("88", matches) | ||||
# should append `]` if matches and item only | ||||
self.assertIn("99]", matches) | ||||
_, matches = complete(line_buffer="d['aa', ") | ||||
# should restrict matches to those matching tuple prefix | ||||
self.assertIn("11]", matches) | ||||
self.assertNotIn("'bb'", matches) | ||||
self.assertNotIn("'bb', ", matches) | ||||
self.assertNotIn("'bb']", matches) | ||||
self.assertNotIn("'cc'", matches) | ||||
self.assertNotIn("'cc', ", matches) | ||||
self.assertNotIn("'cc']", matches) | ||||
ip.Completer.auto_close_dict_keys = False | ||||
krassowski
|
r27906 | |||
Matthias Bussonnier
|
r25099 | def test_dict_key_completion_string(self): | ||
"""Test dictionary key completion for string keys""" | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
Fernando Perez
|
r6991 | |||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = {"abc": None} | ||
Fernando Perez
|
r6991 | |||
Matthias Bussonnier
|
r25099 | # check completion at different stages | ||
_, matches = complete(line_buffer="d[") | ||||
Samuel Gaist
|
r26888 | self.assertIn("'abc'", matches) | ||
self.assertNotIn("'abc']", matches) | ||||
Fernando Perez
|
r6991 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['") | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn("abc']", matches) | ||||
Fernando Perez
|
r6991 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['a") | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn("abc']", matches) | ||||
David P. Sanders
|
r13344 | |||
Matthias Bussonnier
|
r25099 | # check use of different quoting | ||
Matthias Bussonnier
|
r25101 | _, matches = complete(line_buffer='d["') | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn('abc"]', matches) | ||||
David P. Sanders
|
r13344 | |||
Matthias Bussonnier
|
r25101 | _, matches = complete(line_buffer='d["a') | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn('abc"]', matches) | ||||
David P. Sanders
|
r13344 | |||
Matthias Bussonnier
|
r25099 | # check sensitivity to following context | ||
_, matches = complete(line_buffer="d[]", cursor_pos=2) | ||||
Samuel Gaist
|
r26888 | self.assertIn("'abc'", matches) | ||
Matthias Bussonnier
|
r23898 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['']", cursor_pos=3) | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn("abc'", matches) | ||||
self.assertNotIn("abc']", matches) | ||||
Matthias Bussonnier
|
r23898 | |||
Matthias Bussonnier
|
r25099 | # check multiple solutions are correctly returned and that noise is not | ||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = { | ||
"abc": None, | ||||
"abd": None, | ||||
"bad": None, | ||||
object(): None, | ||||
5: None, | ||||
Corentin Cadiou
|
r25852 | ("abe", None): None, | ||
(None, "abf"): None | ||||
Matthias Bussonnier
|
r25101 | } | ||
Matthias Bussonnier
|
r23898 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['a") | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertIn("abd", matches) | ||||
self.assertNotIn("bad", matches) | ||||
self.assertNotIn("abe", matches) | ||||
self.assertNotIn("abf", matches) | ||||
Matthias Bussonnier
|
r25101 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches | ||
Matthias Bussonnier
|
r25099 | |||
# check escaping and whitespace | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None} | ||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['a") | ||
Samuel Gaist
|
r26888 | self.assertIn("a\\nb", matches) | ||
self.assertIn("a\\'b", matches) | ||||
self.assertIn('a"b', matches) | ||||
self.assertIn("a word", matches) | ||||
Matthias Bussonnier
|
r25101 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches | ||
Matthias Bussonnier
|
r25099 | |||
# - can complete on non-initial word of the string | ||||
_, matches = complete(line_buffer="d['a w") | ||||
Samuel Gaist
|
r26888 | self.assertIn("word", matches) | ||
Matthias Bussonnier
|
r25099 | |||
# - understands quote escaping | ||||
_, matches = complete(line_buffer="d['a\\'") | ||||
Samuel Gaist
|
r26888 | self.assertIn("b", matches) | ||
Matthias Bussonnier
|
r25099 | |||
# - default quoting should work like repr | ||||
_, matches = complete(line_buffer="d[") | ||||
Samuel Gaist
|
r26888 | self.assertIn('"a\'b"', matches) | ||
Matthias Bussonnier
|
r25099 | |||
# - when opening quote with ", possible to match with unescaped apostrophe | ||||
_, matches = complete(line_buffer="d[\"a'") | ||||
Samuel Gaist
|
r26888 | self.assertIn("b", matches) | ||
Matthias Bussonnier
|
r25099 | |||
# need to not split at delims that readline won't split at | ||||
Matthias Bussonnier
|
r25101 | if "-" not in ip.Completer.splitter.delims: | ||
ip.user_ns["d"] = {"before-after": None} | ||||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['before-af") | ||
Samuel Gaist
|
r26888 | self.assertIn("before-after", matches) | ||
Matthias Bussonnier
|
r25099 | |||
Corentin Cadiou
|
r25853 | # check completion on tuple-of-string keys at different stage - on first key | ||
ip.user_ns["d"] = {('foo', 'bar'): None} | ||||
_, matches = complete(line_buffer="d[") | ||||
Samuel Gaist
|
r26888 | self.assertIn("'foo'", matches) | ||
self.assertNotIn("'foo']", matches) | ||||
self.assertNotIn("'bar'", matches) | ||||
self.assertNotIn("foo", matches) | ||||
self.assertNotIn("bar", matches) | ||||
Corentin Cadiou
|
r25853 | |||
# - match the prefix | ||||
_, matches = complete(line_buffer="d['f") | ||||
Samuel Gaist
|
r26888 | self.assertIn("foo", matches) | ||
self.assertNotIn("foo']", matches) | ||||
self.assertNotIn('foo"]', matches) | ||||
Corentin Cadiou
|
r25853 | _, matches = complete(line_buffer="d['foo") | ||
Samuel Gaist
|
r26888 | self.assertIn("foo", matches) | ||
Corentin Cadiou
|
r25853 | |||
# - can complete on second key | ||||
_, matches = complete(line_buffer="d['foo', ") | ||||
Samuel Gaist
|
r26888 | self.assertIn("'bar'", matches) | ||
Corentin Cadiou
|
r25853 | _, matches = complete(line_buffer="d['foo', 'b") | ||
Samuel Gaist
|
r26888 | self.assertIn("bar", matches) | ||
self.assertNotIn("foo", matches) | ||||
Corentin Cadiou
|
r25853 | |||
# - does not propose missing keys | ||||
_, matches = complete(line_buffer="d['foo', 'f") | ||||
Samuel Gaist
|
r26888 | self.assertNotIn("bar", matches) | ||
self.assertNotIn("foo", matches) | ||||
Corentin Cadiou
|
r25853 | |||
# check sensitivity to following context | ||||
_, matches = complete(line_buffer="d['foo',]", cursor_pos=8) | ||||
Samuel Gaist
|
r26888 | self.assertIn("'bar'", matches) | ||
self.assertNotIn("bar", matches) | ||||
self.assertNotIn("'foo'", matches) | ||||
self.assertNotIn("foo", matches) | ||||
Corentin Cadiou
|
r25853 | |||
_, matches = complete(line_buffer="d['']", cursor_pos=3) | ||||
Samuel Gaist
|
r26888 | self.assertIn("foo", matches) | ||
Corentin Cadiou
|
r25853 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches | ||
_, matches = complete(line_buffer='d[""]', cursor_pos=3) | ||||
Samuel Gaist
|
r26888 | self.assertIn("foo", matches) | ||
Corentin Cadiou
|
r25853 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches | ||
_, matches = complete(line_buffer='d["foo","]', cursor_pos=9) | ||||
Samuel Gaist
|
r26888 | self.assertIn("bar", matches) | ||
Corentin Cadiou
|
r25853 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches | ||
_, matches = complete(line_buffer='d["foo",]', cursor_pos=8) | ||||
Samuel Gaist
|
r26888 | self.assertIn("'bar'", matches) | ||
self.assertNotIn("bar", matches) | ||||
Corentin Cadiou
|
r25853 | |||
# Can complete with longer tuple keys | ||||
ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None} | ||||
# - can complete second key | ||||
_, matches = complete(line_buffer="d['foo', 'b") | ||||
Samuel Gaist
|
r26888 | self.assertIn("bar", matches) | ||
self.assertNotIn("foo", matches) | ||||
self.assertNotIn("foobar", matches) | ||||
Corentin Cadiou
|
r25853 | |||
# - can complete third key | ||||
_, matches = complete(line_buffer="d['foo', 'bar', 'fo") | ||||
Samuel Gaist
|
r26888 | self.assertIn("foobar", matches) | ||
self.assertNotIn("foo", matches) | ||||
self.assertNotIn("bar", matches) | ||||
Corentin Cadiou
|
r25853 | |||
krassowski
|
r27912 | def test_dict_key_completion_numbers(self): | ||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
ip.user_ns["d"] = { | ||||
krassowski
|
r27913 | 0xDEADBEEF: None, # 3735928559 | ||
krassowski
|
r27912 | 1111: None, | ||
1234: None, | ||||
"1999": None, | ||||
krassowski
|
r27913 | 0b10101: None, # 21 | ||
22: None, | ||||
krassowski
|
r27912 | } | ||
_, matches = complete(line_buffer="d[1") | ||||
self.assertIn("1111", matches) | ||||
self.assertIn("1234", matches) | ||||
self.assertNotIn("1999", matches) | ||||
self.assertNotIn("'1999'", matches) | ||||
_, matches = complete(line_buffer="d[0xdead") | ||||
self.assertIn("0xdeadbeef", matches) | ||||
_, matches = complete(line_buffer="d[2") | ||||
self.assertIn("21", matches) | ||||
self.assertIn("22", matches) | ||||
_, matches = complete(line_buffer="d[0b101") | ||||
self.assertIn("0b10101", matches) | ||||
self.assertIn("0b10110", matches) | ||||
Matthias Bussonnier
|
r25099 | def test_dict_key_completion_contexts(self): | ||
"""Test expression contexts in which dict key completion occurs""" | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
Matthias Bussonnier
|
r25101 | d = {"abc": None} | ||
ip.user_ns["d"] = d | ||||
Matthias Bussonnier
|
r25099 | |||
class C: | ||||
data = d | ||||
Matthias Bussonnier
|
r25101 | |||
ip.user_ns["C"] = C | ||||
ip.user_ns["get"] = lambda: d | ||||
krassowski
|
r27913 | ip.user_ns["nested"] = {"x": d} | ||
Matthias Bussonnier
|
r25099 | |||
def assert_no_completion(**kwargs): | ||||
_, matches = complete(**kwargs) | ||||
Samuel Gaist
|
r26888 | self.assertNotIn("abc", matches) | ||
self.assertNotIn("abc'", matches) | ||||
self.assertNotIn("abc']", matches) | ||||
self.assertNotIn("'abc'", matches) | ||||
self.assertNotIn("'abc']", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def assert_completion(**kwargs): | ||||
_, matches = complete(**kwargs) | ||||
Samuel Gaist
|
r26888 | self.assertIn("'abc'", matches) | ||
self.assertNotIn("'abc']", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
# no completion after string closed, even if reopened | ||||
assert_no_completion(line_buffer="d['a'") | ||||
Matthias Bussonnier
|
r25101 | assert_no_completion(line_buffer='d["a"') | ||
Matthias Bussonnier
|
r25099 | assert_no_completion(line_buffer="d['a' + ") | ||
assert_no_completion(line_buffer="d['a' + '") | ||||
# completion in non-trivial expressions | ||||
assert_completion(line_buffer="+ d[") | ||||
assert_completion(line_buffer="(d[") | ||||
assert_completion(line_buffer="C.data[") | ||||
krassowski
|
r27906 | # nested dict completion | ||
assert_completion(line_buffer="nested['x'][") | ||||
krassowski
|
r27915 | with evaluation_policy("minimal"): | ||
krassowski
|
r27906 | with pytest.raises(AssertionError): | ||
assert_completion(line_buffer="nested['x'][") | ||||
Matthias Bussonnier
|
r25099 | # greedy flag | ||
def assert_completion(**kwargs): | ||||
_, matches = complete(**kwargs) | ||||
Samuel Gaist
|
r26888 | self.assertIn("get()['abc']", matches) | ||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | assert_no_completion(line_buffer="get()[") | ||
with greedy_completion(): | ||||
assert_completion(line_buffer="get()[") | ||||
assert_completion(line_buffer="get()['") | ||||
assert_completion(line_buffer="get()['a") | ||||
assert_completion(line_buffer="get()['ab") | ||||
assert_completion(line_buffer="get()['abc") | ||||
Matthias Bussonnier
|
r23322 | |||
Matthias Bussonnier
|
r25099 | def test_dict_key_completion_bytes(self): | ||
"""Test handling of bytes in dict key completion""" | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = {"abc": None, b"abd": None} | ||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d[") | ||
Samuel Gaist
|
r26888 | self.assertIn("'abc'", matches) | ||
self.assertIn("b'abd'", matches) | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | if False: # not currently implemented | ||
_, matches = complete(line_buffer="d[b") | ||||
Samuel Gaist
|
r26888 | self.assertIn("b'abd'", matches) | ||
self.assertNotIn("b'abc'", matches) | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d[b'") | ||
Samuel Gaist
|
r26888 | self.assertIn("abd", matches) | ||
self.assertNotIn("abc", matches) | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d[B'") | ||
Samuel Gaist
|
r26888 | self.assertIn("abd", matches) | ||
self.assertNotIn("abc", matches) | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['") | ||
Samuel Gaist
|
r26888 | self.assertIn("abc", matches) | ||
self.assertNotIn("abd", matches) | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25099 | def test_dict_key_completion_unicode_py3(self): | ||
"""Test handling of unicode in dict key completion""" | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
Joel Nothman
|
r15773 | |||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = {"a\u05d0": None} | ||
Joel Nothman
|
r15773 | |||
MinRK
|
r16564 | # query using escape | ||
Matthias Bussonnier
|
r25101 | if sys.platform != "win32": | ||
Matthias Bussonnier
|
r25099 | # Known failure on Windows | ||
_, matches = complete(line_buffer="d['a\\u05d0") | ||||
Samuel Gaist
|
r26888 | self.assertIn("u05d0", matches) # tokenized after \\ | ||
MinRK
|
r16564 | |||
# query using character | ||||
_, matches = complete(line_buffer="d['a\u05d0") | ||||
Samuel Gaist
|
r26888 | self.assertIn("a\u05d0", matches) | ||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | with greedy_completion(): | ||
# query using escape | ||||
_, matches = complete(line_buffer="d['a\\u05d0") | ||||
Samuel Gaist
|
r26888 | self.assertIn("d['a\\u05d0']", matches) # tokenized after \\ | ||
Matthias Bussonnier
|
r25099 | |||
# query using character | ||||
_, matches = complete(line_buffer="d['a\u05d0") | ||||
Samuel Gaist
|
r26888 | self.assertIn("d['a\u05d0']", matches) | ||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r25101 | @dec.skip_without("numpy") | ||
Matthias Bussonnier
|
r25099 | def test_struct_array_key_completion(self): | ||
"""Test dict key completion applies to numpy struct arrays""" | ||||
import numpy | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip = get_ipython() | ||
complete = ip.Completer.complete | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")]) | ||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['") | ||
Samuel Gaist
|
r26888 | self.assertIn("hello", matches) | ||
self.assertIn("world", matches) | ||||
Matthias Bussonnier
|
r25099 | # complete on the numpy struct itself | ||
Matthias Bussonnier
|
r25101 | dt = numpy.dtype( | ||
[("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)] | ||||
) | ||||
Matthias Bussonnier
|
r25099 | x = numpy.zeros(2, dtype=dt) | ||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = x[1] | ||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['") | ||
Samuel Gaist
|
r26888 | self.assertIn("my_head", matches) | ||
self.assertIn("my_data", matches) | ||||
krassowski
|
r27913 | |||
krassowski
|
r27906 | def completes_on_nested(): | ||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = numpy.zeros(2, dtype=dt) | ||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d[1]['my_head']['") | ||
Samuel Gaist
|
r26888 | self.assertTrue(any(["my_dt" in m for m in matches])) | ||
self.assertTrue(any(["my_df" in m for m in matches])) | ||||
krassowski
|
r27906 | # complete on a nested level | ||
with greedy_completion(): | ||||
completes_on_nested() | ||||
krassowski
|
r27915 | with evaluation_policy("limited"): | ||
krassowski
|
r27906 | completes_on_nested() | ||
krassowski
|
r27915 | with evaluation_policy("minimal"): | ||
krassowski
|
r27906 | with pytest.raises(AssertionError): | ||
completes_on_nested() | ||||
Matthias Bussonnier
|
r25099 | |||
Matthias Bussonnier
|
r25101 | @dec.skip_without("pandas") | ||
Matthias Bussonnier
|
r25099 | def test_dataframe_key_completion(self): | ||
"""Test dict key completion applies to pandas DataFrames""" | ||||
import pandas | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip = get_ipython() | ||
complete = ip.Completer.complete | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]}) | ||
Matthias Bussonnier
|
r25099 | _, matches = complete(line_buffer="d['") | ||
Samuel Gaist
|
r26888 | self.assertIn("hello", matches) | ||
self.assertIn("world", matches) | ||||
krassowski
|
r27906 | _, matches = complete(line_buffer="d.loc[:, '") | ||
self.assertIn("hello", matches) | ||||
self.assertIn("world", matches) | ||||
_, matches = complete(line_buffer="d.loc[1:, '") | ||||
self.assertIn("hello", matches) | ||||
_, matches = complete(line_buffer="d.loc[1:1, '") | ||||
self.assertIn("hello", matches) | ||||
_, matches = complete(line_buffer="d.loc[1:1:-1, '") | ||||
self.assertIn("hello", matches) | ||||
_, matches = complete(line_buffer="d.loc[::, '") | ||||
self.assertIn("hello", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_dict_key_completion_invalids(self): | ||||
"""Smoke test cases dict key completion can't handle""" | ||||
ip = get_ipython() | ||||
complete = ip.Completer.complete | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["no_getitem"] = None | ||
ip.user_ns["no_keys"] = [] | ||||
ip.user_ns["cant_call_keys"] = dict | ||||
ip.user_ns["empty"] = {} | ||||
ip.user_ns["d"] = {"abc": 5} | ||||
Matthias Bussonnier
|
r25099 | |||
_, matches = complete(line_buffer="no_getitem['") | ||||
_, matches = complete(line_buffer="no_keys['") | ||||
_, matches = complete(line_buffer="cant_call_keys['") | ||||
_, matches = complete(line_buffer="empty['") | ||||
_, matches = complete(line_buffer="name_error['") | ||||
_, matches = complete(line_buffer="d['\\") # incomplete escape | ||||
def test_object_key_completion(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"]) | ||
Matthias Bussonnier
|
r25099 | |||
_, matches = ip.Completer.complete(line_buffer="key_completable['qw") | ||||
Samuel Gaist
|
r26888 | self.assertIn("qwerty", matches) | ||
self.assertIn("qwick", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_class_key_completion(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | NamedInstanceClass("qwerty") | ||
NamedInstanceClass("qwick") | ||||
ip.user_ns["named_instance_class"] = NamedInstanceClass | ||||
Matthias Bussonnier
|
r25099 | |||
_, matches = ip.Completer.complete(line_buffer="named_instance_class['qw") | ||||
Samuel Gaist
|
r26888 | self.assertIn("qwerty", matches) | ||
self.assertIn("qwick", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_tryimport(self): | ||||
""" | ||||
Test that try-import don't crash on trailing dot, and import modules before | ||||
""" | ||||
from IPython.core.completerlib import try_import | ||||
Matthias Bussonnier
|
r25101 | assert try_import("IPython.") | ||
Matthias Bussonnier
|
r25099 | |||
def test_aimport_module_completer(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | _, matches = ip.complete("i", "%aimport i") | ||
Samuel Gaist
|
r26888 | self.assertIn("io", matches) | ||
self.assertNotIn("int", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_nested_import_module_completer(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | _, matches = ip.complete(None, "import IPython.co", 17) | ||
Samuel Gaist
|
r26888 | self.assertIn("IPython.core", matches) | ||
self.assertNotIn("import IPython.core", matches) | ||||
self.assertNotIn("IPython.display", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_import_module_completer(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | _, matches = ip.complete("i", "import i") | ||
Samuel Gaist
|
r26888 | self.assertIn("io", matches) | ||
self.assertNotIn("int", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_from_module_completer(self): | ||||
ip = get_ipython() | ||||
Matthias Bussonnier
|
r25101 | _, matches = ip.complete("B", "from io import B", 16) | ||
Samuel Gaist
|
r26888 | self.assertIn("BytesIO", matches) | ||
self.assertNotIn("BaseException", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_snake_case_completion(self): | ||||
ip = get_ipython() | ||||
ip.Completer.use_jedi = False | ||||
Matthias Bussonnier
|
r25101 | ip.user_ns["some_three"] = 3 | ||
ip.user_ns["some_four"] = 4 | ||||
Matthias Bussonnier
|
r25099 | _, matches = ip.complete("s_", "print(s_f") | ||
Samuel Gaist
|
r26888 | self.assertIn("some_three", matches) | ||
self.assertIn("some_four", matches) | ||||
Matthias Bussonnier
|
r25099 | |||
def test_mix_terms(self): | ||||
ip = get_ipython() | ||||
from textwrap import dedent | ||||
Matthias Bussonnier
|
r25101 | |||
Matthias Bussonnier
|
r25099 | ip.Completer.use_jedi = False | ||
Matthias Bussonnier
|
r25101 | ip.ex( | ||
dedent( | ||||
""" | ||||
Matthias Bussonnier
|
r25099 | class Test: | ||
def meth(self, meth_arg1): | ||||
print("meth") | ||||
def meth_1(self, meth1_arg1, meth1_arg2): | ||||
print("meth1") | ||||
def meth_2(self, meth2_arg1, meth2_arg2): | ||||
print("meth2") | ||||
test = Test() | ||||
Matthias Bussonnier
|
r25101 | """ | ||
) | ||||
) | ||||
Matthias Bussonnier
|
r25099 | _, matches = ip.complete(None, "test.meth(") | ||
Samuel Gaist
|
r26888 | self.assertIn("meth_arg1=", matches) | ||
self.assertNotIn("meth2_arg1=", matches) | ||||
ygeyzel
|
r27541 | |||
def test_percent_symbol_restrict_to_magic_completions(self): | ||||
ip = get_ipython() | ||||
completer = ip.Completer | ||||
text = "%a" | ||||
with provisionalcompleter(): | ||||
completer.use_jedi = True | ||||
completions = completer.completions(text, len(text)) | ||||
for c in completions: | ||||
self.assertEqual(c.text[0], "%") | ||||
krassowski
|
r27775 | |||
krassowski
|
r27786 | def test_fwd_unicode_restricts(self): | ||
ip = get_ipython() | ||||
completer = ip.Completer | ||||
text = "\\ROMAN NUMERAL FIVE" | ||||
with provisionalcompleter(): | ||||
completer.use_jedi = True | ||||
completions = [ | ||||
completion.text for completion in completer.completions(text, len(text)) | ||||
] | ||||
self.assertEqual(completions, ["\u2164"]) | ||||
krassowski
|
r27776 | def test_dict_key_restrict_to_dicts(self): | ||
"""Test that dict key suppresses non-dict completion items""" | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
d = {"abc": None} | ||||
ip.user_ns["d"] = d | ||||
text = 'd["a' | ||||
def _(): | ||||
with provisionalcompleter(): | ||||
c.use_jedi = True | ||||
return [ | ||||
completion.text for completion in c.completions(text, len(text)) | ||||
] | ||||
completions = _() | ||||
self.assertEqual(completions, ["abc"]) | ||||
# check that it can be disabled in granular manner: | ||||
cfg = Config() | ||||
cfg.IPCompleter.suppress_competing_matchers = { | ||||
"IPCompleter.dict_key_matcher": False | ||||
} | ||||
c.update_config(cfg) | ||||
completions = _() | ||||
self.assertIn("abc", completions) | ||||
self.assertGreater(len(completions), 1) | ||||
krassowski
|
r27775 | def test_matcher_suppression(self): | ||
@completion_matcher(identifier="a_matcher") | ||||
def a_matcher(text): | ||||
return ["completion_a"] | ||||
@completion_matcher(identifier="b_matcher", api_version=2) | ||||
def b_matcher(context: CompletionContext): | ||||
text = context.token | ||||
result = {"completions": [SimpleCompletion("completion_b")]} | ||||
if text == "suppress c": | ||||
result["suppress"] = {"c_matcher"} | ||||
if text.startswith("suppress all"): | ||||
result["suppress"] = True | ||||
if text == "suppress all but c": | ||||
result["do_not_suppress"] = {"c_matcher"} | ||||
if text == "suppress all but a": | ||||
result["do_not_suppress"] = {"a_matcher"} | ||||
return result | ||||
@completion_matcher(identifier="c_matcher") | ||||
def c_matcher(text): | ||||
return ["completion_c"] | ||||
with custom_matchers([a_matcher, b_matcher, c_matcher]): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
def _(text, expected): | ||||
krassowski
|
r27776 | c.use_jedi = False | ||
s, matches = c.complete(text) | ||||
self.assertEqual(expected, matches) | ||||
krassowski
|
r27775 | |||
_("do not suppress", ["completion_a", "completion_b", "completion_c"]) | ||||
_("suppress all", ["completion_b"]) | ||||
_("suppress all but a", ["completion_a", "completion_b"]) | ||||
_("suppress all but c", ["completion_b", "completion_c"]) | ||||
krassowski
|
r27776 | def configure(suppression_config): | ||
cfg = Config() | ||||
cfg.IPCompleter.suppress_competing_matchers = suppression_config | ||||
c.update_config(cfg) | ||||
# test that configuration takes priority over the run-time decisions | ||||
configure(False) | ||||
_("suppress all", ["completion_a", "completion_b", "completion_c"]) | ||||
configure({"b_matcher": False}) | ||||
_("suppress all", ["completion_a", "completion_b", "completion_c"]) | ||||
configure({"a_matcher": False}) | ||||
_("suppress all", ["completion_b"]) | ||||
configure({"b_matcher": True}) | ||||
_("do not suppress", ["completion_b"]) | ||||
krassowski
|
r27877 | configure(True) | ||
_("do not suppress", ["completion_a"]) | ||||
def test_matcher_suppression_with_iterator(self): | ||||
@completion_matcher(identifier="matcher_returning_iterator") | ||||
def matcher_returning_iterator(text): | ||||
return iter(["completion_iter"]) | ||||
@completion_matcher(identifier="matcher_returning_list") | ||||
def matcher_returning_list(text): | ||||
return ["completion_list"] | ||||
with custom_matchers([matcher_returning_iterator, matcher_returning_list]): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
def _(text, expected): | ||||
c.use_jedi = False | ||||
s, matches = c.complete(text) | ||||
self.assertEqual(expected, matches) | ||||
def configure(suppression_config): | ||||
cfg = Config() | ||||
cfg.IPCompleter.suppress_competing_matchers = suppression_config | ||||
c.update_config(cfg) | ||||
configure(False) | ||||
_("---", ["completion_iter", "completion_list"]) | ||||
configure(True) | ||||
_("---", ["completion_iter"]) | ||||
configure(None) | ||||
_("--", ["completion_iter", "completion_list"]) | ||||
Matthias Bussonnier
|
r28561 | @pytest.mark.xfail( | ||
sys.version_info.releaselevel in ("alpha",), | ||||
reason="Parso does not yet parse 3.13", | ||||
) | ||||
krassowski
|
r27877 | def test_matcher_suppression_with_jedi(self): | ||
ip = get_ipython() | ||||
c = ip.Completer | ||||
c.use_jedi = True | ||||
def configure(suppression_config): | ||||
cfg = Config() | ||||
cfg.IPCompleter.suppress_competing_matchers = suppression_config | ||||
c.update_config(cfg) | ||||
def _(): | ||||
with provisionalcompleter(): | ||||
matches = [completion.text for completion in c.completions("dict.", 5)] | ||||
self.assertIn("keys", matches) | ||||
configure(False) | ||||
_() | ||||
configure(True) | ||||
_() | ||||
configure(None) | ||||
_() | ||||
krassowski
|
r27775 | def test_matcher_disabling(self): | ||
@completion_matcher(identifier="a_matcher") | ||||
def a_matcher(text): | ||||
return ["completion_a"] | ||||
@completion_matcher(identifier="b_matcher") | ||||
def b_matcher(text): | ||||
return ["completion_b"] | ||||
def _(expected): | ||||
krassowski
|
r27776 | s, matches = c.complete("completion_") | ||
self.assertEqual(expected, matches) | ||||
krassowski
|
r27775 | |||
with custom_matchers([a_matcher, b_matcher]): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
_(["completion_a", "completion_b"]) | ||||
cfg = Config() | ||||
cfg.IPCompleter.disable_matchers = ["b_matcher"] | ||||
c.update_config(cfg) | ||||
_(["completion_a"]) | ||||
cfg.IPCompleter.disable_matchers = [] | ||||
c.update_config(cfg) | ||||
def test_matcher_priority(self): | ||||
@completion_matcher(identifier="a_matcher", priority=0, api_version=2) | ||||
def a_matcher(text): | ||||
return {"completions": [SimpleCompletion("completion_a")], "suppress": True} | ||||
@completion_matcher(identifier="b_matcher", priority=2, api_version=2) | ||||
def b_matcher(text): | ||||
return {"completions": [SimpleCompletion("completion_b")], "suppress": True} | ||||
def _(expected): | ||||
krassowski
|
r27776 | s, matches = c.complete("completion_") | ||
self.assertEqual(expected, matches) | ||||
krassowski
|
r27775 | |||
with custom_matchers([a_matcher, b_matcher]): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
_(["completion_b"]) | ||||
a_matcher.matcher_priority = 3 | ||||
_(["completion_a"]) | ||||
krassowski
|
r27912 | |||
@pytest.mark.parametrize( | ||||
M Bussonnier
|
r28976 | "setup,code,expected,not_expected", | ||
[ | ||||
('a="str"; b=1', "(a, b.", [".bit_count", ".conjugate"], [".count"]), | ||||
('a="str"; b=1', "(a, b).", [".count"], [".bit_count", ".capitalize"]), | ||||
('x="str"; y=1', "x = {1, y.", [".bit_count"], [".count"]), | ||||
('x="str"; y=1', "x = [1, y.", [".bit_count"], [".count"]), | ||||
('x="str"; y=1; fun=lambda x:x', "x = fun(1, y.", [".bit_count"], [".count"]), | ||||
], | ||||
) | ||||
def test_misc_no_jedi_completions(setup, code, expected, not_expected): | ||||
ip = get_ipython() | ||||
c = ip.Completer | ||||
ip.ex(setup) | ||||
with provisionalcompleter(), jedi_status(False): | ||||
matches = c.all_completions(code) | ||||
assert set(expected) - set(matches) == set(), set(matches) | ||||
assert set(matches).intersection(set(not_expected)) == set() | ||||
@pytest.mark.parametrize( | ||||
"code,expected", | ||||
[ | ||||
(" (a, b", "b"), | ||||
("(a, b", "b"), | ||||
("(a, b)", ""), # trim always start by trimming | ||||
(" (a, b)", "(a, b)"), | ||||
(" [a, b]", "[a, b]"), | ||||
(" a, b", "b"), | ||||
("x = {1, y", "y"), | ||||
("x = [1, y", "y"), | ||||
("x = fun(1, y", "y"), | ||||
], | ||||
) | ||||
def test_trim_expr(code, expected): | ||||
c = get_ipython().Completer | ||||
assert c._trim_expr(code) == expected | ||||
@pytest.mark.parametrize( | ||||
krassowski
|
r27913 | "input, expected", | ||
krassowski
|
r27912 | [ | ||
krassowski
|
r27913 | ["1.234", "1.234"], | ||
krassowski
|
r27912 | # should match signed numbers | ||
krassowski
|
r27913 | ["+1", "+1"], | ||
["-1", "-1"], | ||||
["-1.0", "-1.0"], | ||||
["-1.", "-1."], | ||||
["+1.", "+1."], | ||||
[".1", ".1"], | ||||
krassowski
|
r27912 | # should not match non-numbers | ||
krassowski
|
r27913 | ["1..", None], | ||
["..", None], | ||||
[".1.", None], | ||||
krassowski
|
r27912 | # should match after comma | ||
krassowski
|
r27913 | [",1", "1"], | ||
[", 1", "1"], | ||||
[", .1", ".1"], | ||||
[", +.1", "+.1"], | ||||
krassowski
|
r27912 | # should not match after trailing spaces | ||
krassowski
|
r27913 | [".1 ", None], | ||
krassowski
|
r27912 | # some complex cases | ||
krassowski
|
r27913 | ["0b_0011_1111_0100_1110", "0b_0011_1111_0100_1110"], | ||
["0xdeadbeef", "0xdeadbeef"], | ||||
["0b_1110_0101", "0b_1110_0101"], | ||||
krassowski
|
r27927 | # should not match if in an operation | ||
["1 + 1", None], | ||||
[", 1 + 1", None], | ||||
krassowski
|
r27913 | ], | ||
krassowski
|
r27912 | ) | ||
def test_match_numeric_literal_for_dict_key(input, expected): | ||||
assert _match_number_in_dict_key_prefix(input) == expected | ||||