##// END OF EJS Templates
fix IPCompleter inside tuples/arrays when jedi is disabled...
M Bussonnier -
Show More
@@ -184,6 +184,7 import glob
184 import inspect
184 import inspect
185 import itertools
185 import itertools
186 import keyword
186 import keyword
187 import ast
187 import os
188 import os
188 import re
189 import re
189 import string
190 import string
@@ -449,11 +450,11 def completions_sorting_key(word):
449
450
450 if word.startswith('%%'):
451 if word.startswith('%%'):
451 # If there's another % in there, this is something else, so leave it alone
452 # If there's another % in there, this is something else, so leave it alone
452 if not "%" in word[2:]:
453 if "%" not in word[2:]:
453 word = word[2:]
454 word = word[2:]
454 prio2 = 2
455 prio2 = 2
455 elif word.startswith('%'):
456 elif word.startswith('%'):
456 if not "%" in word[1:]:
457 if "%" not in word[1:]:
457 word = word[1:]
458 word = word[1:]
458 prio2 = 1
459 prio2 = 1
459
460
@@ -961,8 +962,8 class CompletionSplitter(object):
961 def split_line(self, line, cursor_pos=None):
962 def split_line(self, line, cursor_pos=None):
962 """Split a line of text with a cursor at the given position.
963 """Split a line of text with a cursor at the given position.
963 """
964 """
964 l = line if cursor_pos is None else line[:cursor_pos]
965 cut_line = line if cursor_pos is None else line[:cursor_pos]
965 return self._delim_re.split(l)[-1]
966 return self._delim_re.split(cut_line)[-1]
966
967
967
968
968
969
@@ -1141,8 +1142,12 class Completer(Configurable):
1141 """
1142 """
1142 return self._attr_matches(text)[0]
1143 return self._attr_matches(text)[0]
1143
1144
1145 # we simple attribute matching with normal identifiers.
1146 _ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$")
1147
1144 def _attr_matches(self, text, include_prefix=True) -> Tuple[Sequence[str], str]:
1148 def _attr_matches(self, text, include_prefix=True) -> Tuple[Sequence[str], str]:
1145 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1149
1150 m2 = self._ATTR_MATCH_RE.match(self.line_buffer)
1146 if not m2:
1151 if not m2:
1147 return [], ""
1152 return [], ""
1148 expr, attr = m2.group(1, 2)
1153 expr, attr = m2.group(1, 2)
@@ -1204,6 +1209,30 class Completer(Configurable):
1204 "." + attr,
1209 "." + attr,
1205 )
1210 )
1206
1211
1212 def _trim_expr(self, code: str) -> str:
1213 """
1214 Trim the code until it is a valid expression and not a tuple;
1215
1216 return the trimmed expression for guarded_eval.
1217 """
1218 while code:
1219 code = code[1:]
1220 try:
1221 res = ast.parse(code)
1222 except SyntaxError:
1223 continue
1224
1225 assert res is not None
1226 if len(res.body) != 1:
1227 continue
1228 expr = res.body[0].value
1229 if isinstance(expr, ast.Tuple) and not code[-1] == ")":
1230 # we skip implicit tuple, like when trimming `fun(a,b`<completion>
1231 # as `a,b` would be a tuple, and we actually expect to get only `b`
1232 continue
1233 return code
1234 return ""
1235
1207 def _evaluate_expr(self, expr):
1236 def _evaluate_expr(self, expr):
1208 obj = not_found
1237 obj = not_found
1209 done = False
1238 done = False
@@ -1225,14 +1254,14 class Completer(Configurable):
1225 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1254 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1226 # where parenthesis is not closed.
1255 # where parenthesis is not closed.
1227 # TODO: make this faster by reusing parts of the computation?
1256 # TODO: make this faster by reusing parts of the computation?
1228 expr = expr[1:]
1257 expr = self._trim_expr(expr)
1229 return obj
1258 return obj
1230
1259
1231 def get__all__entries(obj):
1260 def get__all__entries(obj):
1232 """returns the strings in the __all__ attribute"""
1261 """returns the strings in the __all__ attribute"""
1233 try:
1262 try:
1234 words = getattr(obj, '__all__')
1263 words = getattr(obj, '__all__')
1235 except:
1264 except Exception:
1236 return []
1265 return []
1237
1266
1238 return [w for w in words if isinstance(w, str)]
1267 return [w for w in words if isinstance(w, str)]
@@ -1447,7 +1476,7 def match_dict_keys(
1447 try:
1476 try:
1448 if not str_key.startswith(prefix_str):
1477 if not str_key.startswith(prefix_str):
1449 continue
1478 continue
1450 except (AttributeError, TypeError, UnicodeError) as e:
1479 except (AttributeError, TypeError, UnicodeError):
1451 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1480 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1452 continue
1481 continue
1453
1482
@@ -1495,7 +1524,7 def cursor_to_position(text:str, line:int, column:int)->int:
1495 lines = text.split('\n')
1524 lines = text.split('\n')
1496 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1525 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1497
1526
1498 return sum(len(l) + 1 for l in lines[:line]) + column
1527 return sum(len(line) + 1 for line in lines[:line]) + column
1499
1528
1500 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1529 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1501 """
1530 """
@@ -2469,7 +2498,8 class IPCompleter(Completer):
2469 # parenthesis before the cursor
2498 # parenthesis before the cursor
2470 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
2499 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
2471 tokens = regexp.findall(self.text_until_cursor)
2500 tokens = regexp.findall(self.text_until_cursor)
2472 iterTokens = reversed(tokens); openPar = 0
2501 iterTokens = reversed(tokens)
2502 openPar = 0
2473
2503
2474 for token in iterTokens:
2504 for token in iterTokens:
2475 if token == ')':
2505 if token == ')':
@@ -2489,7 +2519,8 class IPCompleter(Completer):
2489 try:
2519 try:
2490 ids.append(next(iterTokens))
2520 ids.append(next(iterTokens))
2491 if not isId(ids[-1]):
2521 if not isId(ids[-1]):
2492 ids.pop(); break
2522 ids.pop()
2523 break
2493 if not next(iterTokens) == '.':
2524 if not next(iterTokens) == '.':
2494 break
2525 break
2495 except StopIteration:
2526 except StopIteration:
@@ -3215,7 +3246,7 class IPCompleter(Completer):
3215 else:
3246 else:
3216 api_version = _get_matcher_api_version(matcher)
3247 api_version = _get_matcher_api_version(matcher)
3217 raise ValueError(f"Unsupported API version {api_version}")
3248 raise ValueError(f"Unsupported API version {api_version}")
3218 except:
3249 except BaseException:
3219 # Show the ugly traceback if the matcher causes an
3250 # Show the ugly traceback if the matcher causes an
3220 # exception, but do NOT crash the kernel!
3251 # exception, but do NOT crash the kernel!
3221 sys.excepthook(*sys.exc_info())
3252 sys.excepthook(*sys.exc_info())
@@ -9,10 +9,10 import pytest
9 import sys
9 import sys
10 import textwrap
10 import textwrap
11 import unittest
11 import unittest
12 import random
12
13
13 from importlib.metadata import version
14 from importlib.metadata import version
14
15
15
16 from contextlib import contextmanager
16 from contextlib import contextmanager
17
17
18 from traitlets.config.loader import Config
18 from traitlets.config.loader import Config
@@ -21,6 +21,7 from IPython.core import completer
21 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
21 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
22 from IPython.utils.generics import complete_object
22 from IPython.utils.generics import complete_object
23 from IPython.testing import decorators as dec
23 from IPython.testing import decorators as dec
24 from IPython.core.latex_symbols import latex_symbols
24
25
25 from IPython.core.completer import (
26 from IPython.core.completer import (
26 Completion,
27 Completion,
@@ -31,11 +32,24 from IPython.core.completer import (
31 completion_matcher,
32 completion_matcher,
32 SimpleCompletion,
33 SimpleCompletion,
33 CompletionContext,
34 CompletionContext,
35 _unicode_name_compute,
36 _UNICODE_RANGES,
34 )
37 )
35
38
36 from packaging.version import parse
39 from packaging.version import parse
37
40
38
41
42 @contextmanager
43 def jedi_status(status: bool):
44 completer = get_ipython().Completer
45 try:
46 old = completer.use_jedi
47 completer.use_jedi = status
48 yield
49 finally:
50 completer.use_jedi = old
51
52
39 # -----------------------------------------------------------------------------
53 # -----------------------------------------------------------------------------
40 # Test functions
54 # Test functions
41 # -----------------------------------------------------------------------------
55 # -----------------------------------------------------------------------------
@@ -66,7 +80,7 def recompute_unicode_ranges():
66 rg = list(ranges(valid))
80 rg = list(ranges(valid))
67 lens = []
81 lens = []
68 gap_lens = []
82 gap_lens = []
69 pstart, pstop = 0, 0
83 _pstart, pstop = 0, 0
70 for start, stop in rg:
84 for start, stop in rg:
71 lens.append(stop - start)
85 lens.append(stop - start)
72 gap_lens.append(
86 gap_lens.append(
@@ -77,7 +91,7 def recompute_unicode_ranges():
77 f"{round((start - pstop)/0xe01f0*100)}%",
91 f"{round((start - pstop)/0xe01f0*100)}%",
78 )
92 )
79 )
93 )
80 pstart, pstop = start, stop
94 _pstart, pstop = start, stop
81
95
82 return sorted(gap_lens)[-1]
96 return sorted(gap_lens)[-1]
83
97
@@ -87,7 +101,6 def test_unicode_range():
87 Test that the ranges we test for unicode names give the same number of
101 Test that the ranges we test for unicode names give the same number of
88 results than testing the full length.
102 results than testing the full length.
89 """
103 """
90 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
91
104
92 expected_list = _unicode_name_compute([(0, 0x110000)])
105 expected_list = _unicode_name_compute([(0, 0x110000)])
93 test = _unicode_name_compute(_UNICODE_RANGES)
106 test = _unicode_name_compute(_UNICODE_RANGES)
@@ -148,45 +161,45 def custom_matchers(matchers):
148 ip.Completer.custom_matchers.clear()
161 ip.Completer.custom_matchers.clear()
149
162
150
163
151 def test_protect_filename():
164 if sys.platform == "win32":
152 if sys.platform == "win32":
165 pairs = [
153 pairs = [
166 ("abc", "abc"),
154 ("abc", "abc"),
167 (" abc", '" abc"'),
155 (" abc", '" abc"'),
168 ("a bc", '"a bc"'),
156 ("a bc", '"a bc"'),
169 ("a bc", '"a bc"'),
157 ("a bc", '"a bc"'),
170 (" bc", '" bc"'),
158 (" bc", '" bc"'),
171 ]
159 ]
172 else:
160 else:
173 pairs = [
161 pairs = [
174 ("abc", "abc"),
162 ("abc", "abc"),
175 (" abc", r"\ abc"),
163 (" abc", r"\ abc"),
176 ("a bc", r"a\ bc"),
164 ("a bc", r"a\ bc"),
177 ("a bc", r"a\ \ bc"),
165 ("a bc", r"a\ \ bc"),
178 (" bc", r"\ \ bc"),
166 (" bc", r"\ \ bc"),
179 # On posix, we also protect parens and other special characters.
167 # On posix, we also protect parens and other special characters.
180 ("a(bc", r"a\(bc"),
168 ("a(bc", r"a\(bc"),
181 ("a)bc", r"a\)bc"),
169 ("a)bc", r"a\)bc"),
182 ("a( )bc", r"a\(\ \)bc"),
170 ("a( )bc", r"a\(\ \)bc"),
183 ("a[1]bc", r"a\[1\]bc"),
171 ("a[1]bc", r"a\[1\]bc"),
184 ("a{1}bc", r"a\{1\}bc"),
172 ("a{1}bc", r"a\{1\}bc"),
185 ("a#bc", r"a\#bc"),
173 ("a#bc", r"a\#bc"),
186 ("a?bc", r"a\?bc"),
174 ("a?bc", r"a\?bc"),
187 ("a=bc", r"a\=bc"),
175 ("a=bc", r"a\=bc"),
188 ("a\\bc", r"a\\bc"),
176 ("a\\bc", r"a\\bc"),
189 ("a|bc", r"a\|bc"),
177 ("a|bc", r"a\|bc"),
190 ("a;bc", r"a\;bc"),
178 ("a;bc", r"a\;bc"),
191 ("a:bc", r"a\:bc"),
179 ("a:bc", r"a\:bc"),
192 ("a'bc", r"a\'bc"),
180 ("a'bc", r"a\'bc"),
193 ("a*bc", r"a\*bc"),
181 ("a*bc", r"a\*bc"),
194 ('a"bc', r"a\"bc"),
182 ('a"bc', r"a\"bc"),
195 ("a^bc", r"a\^bc"),
183 ("a^bc", r"a\^bc"),
196 ("a&bc", r"a\&bc"),
184 ("a&bc", r"a\&bc"),
197 ]
185 ]
198
186 # run the actual tests
199
187 for s1, s2 in pairs:
200 @pytest.mark.parametrize("s1,expected", pairs)
188 s1p = completer.protect_filename(s1)
201 def test_protect_filename(s1, expected):
189 assert s1p == s2
202 assert completer.protect_filename(s1) == expected
190
203
191
204
192 def check_line_split(splitter, test_specs):
205 def check_line_split(splitter, test_specs):
@@ -297,8 +310,6 class TestCompleter(unittest.TestCase):
297 self.assertIsInstance(matches, list)
310 self.assertIsInstance(matches, list)
298
311
299 def test_latex_completions(self):
312 def test_latex_completions(self):
300 from IPython.core.latex_symbols import latex_symbols
301 import random
302
313
303 ip = get_ipython()
314 ip = get_ipython()
304 # Test some random unicode symbols
315 # Test some random unicode symbols
@@ -1735,6 +1746,45 class TestCompleter(unittest.TestCase):
1735
1746
1736
1747
1737 @pytest.mark.parametrize(
1748 @pytest.mark.parametrize(
1749 "setup,code,expected,not_expected",
1750 [
1751 ('a="str"; b=1', "(a, b.", [".bit_count", ".conjugate"], [".count"]),
1752 ('a="str"; b=1', "(a, b).", [".count"], [".bit_count", ".capitalize"]),
1753 ('x="str"; y=1', "x = {1, y.", [".bit_count"], [".count"]),
1754 ('x="str"; y=1', "x = [1, y.", [".bit_count"], [".count"]),
1755 ('x="str"; y=1; fun=lambda x:x', "x = fun(1, y.", [".bit_count"], [".count"]),
1756 ],
1757 )
1758 def test_misc_no_jedi_completions(setup, code, expected, not_expected):
1759 ip = get_ipython()
1760 c = ip.Completer
1761 ip.ex(setup)
1762 with provisionalcompleter(), jedi_status(False):
1763 matches = c.all_completions(code)
1764 assert set(expected) - set(matches) == set(), set(matches)
1765 assert set(matches).intersection(set(not_expected)) == set()
1766
1767
1768 @pytest.mark.parametrize(
1769 "code,expected",
1770 [
1771 (" (a, b", "b"),
1772 ("(a, b", "b"),
1773 ("(a, b)", ""), # trim always start by trimming
1774 (" (a, b)", "(a, b)"),
1775 (" [a, b]", "[a, b]"),
1776 (" a, b", "b"),
1777 ("x = {1, y", "y"),
1778 ("x = [1, y", "y"),
1779 ("x = fun(1, y", "y"),
1780 ],
1781 )
1782 def test_trim_expr(code, expected):
1783 c = get_ipython().Completer
1784 assert c._trim_expr(code) == expected
1785
1786
1787 @pytest.mark.parametrize(
1738 "input, expected",
1788 "input, expected",
1739 [
1789 [
1740 ["1.234", "1.234"],
1790 ["1.234", "1.234"],
General Comments 0
You need to be logged in to leave comments. Login now