##// END OF EJS Templates
Re-implement key closing behaviour with a setting,...
krassowski -
Show More
@@ -178,6 +178,7 b' The suppression behaviour can is user-configurable via'
178
178
179 from __future__ import annotations
179 from __future__ import annotations
180 import builtins as builtin_mod
180 import builtins as builtin_mod
181 import enum
181 import glob
182 import glob
182 import inspect
183 import inspect
183 import itertools
184 import itertools
@@ -186,15 +187,16 b' import os'
186 import re
187 import re
187 import string
188 import string
188 import sys
189 import sys
190 import tokenize
189 import time
191 import time
190 import unicodedata
192 import unicodedata
191 import uuid
193 import uuid
192 import warnings
194 import warnings
193 from ast import literal_eval
195 from ast import literal_eval
196 from collections import defaultdict
194 from contextlib import contextmanager
197 from contextlib import contextmanager
195 from dataclasses import dataclass
198 from dataclasses import dataclass
196 from functools import cached_property, partial
199 from functools import cached_property, partial
197 from importlib import import_module
198 from types import SimpleNamespace
200 from types import SimpleNamespace
199 from typing import (
201 from typing import (
200 Iterable,
202 Iterable,
@@ -205,8 +207,6 b' from typing import ('
205 Any,
207 Any,
206 Sequence,
208 Sequence,
207 Dict,
209 Dict,
208 NamedTuple,
209 Pattern,
210 Optional,
210 Optional,
211 TYPE_CHECKING,
211 TYPE_CHECKING,
212 Set,
212 Set,
@@ -233,7 +233,6 b' from traitlets import ('
233 Unicode,
233 Unicode,
234 Dict as DictTrait,
234 Dict as DictTrait,
235 Union as UnionTrait,
235 Union as UnionTrait,
236 default,
237 observe,
236 observe,
238 )
237 )
239 from traitlets.config.configurable import Configurable
238 from traitlets.config.configurable import Configurable
@@ -559,7 +558,7 b' class SimpleCompletion:'
559
558
560 __slots__ = ["text", "type"]
559 __slots__ = ["text", "type"]
561
560
562 def __init__(self, text: str, *, type: str = None):
561 def __init__(self, text: str, *, type: Optional[str] = None):
563 self.text = text
562 self.text = text
564 self.type = type
563 self.type = type
565
564
@@ -647,16 +646,18 b' MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]'
647
646
648
647
649 class _MatcherAPIv1Base(Protocol):
648 class _MatcherAPIv1Base(Protocol):
650 def __call__(self, text: str) -> list[str]:
649 def __call__(self, text: str) -> List[str]:
651 """Call signature."""
650 """Call signature."""
651 ...
652
652
653
653
654 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
654 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
655 #: API version
655 #: API version
656 matcher_api_version: Optional[Literal[1]]
656 matcher_api_version: Optional[Literal[1]]
657
657
658 def __call__(self, text: str) -> list[str]:
658 def __call__(self, text: str) -> List[str]:
659 """Call signature."""
659 """Call signature."""
660 ...
660
661
661
662
662 #: Protocol describing Matcher API v1.
663 #: Protocol describing Matcher API v1.
@@ -671,6 +672,7 b' class MatcherAPIv2(Protocol):'
671
672
672 def __call__(self, context: CompletionContext) -> MatcherResult:
673 def __call__(self, context: CompletionContext) -> MatcherResult:
673 """Call signature."""
674 """Call signature."""
675 ...
674
676
675
677
676 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
678 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
@@ -912,10 +914,11 b' class Completer(Configurable):'
912 help="""Activate greedy completion.
914 help="""Activate greedy completion.
913
915
914 .. deprecated:: 8.8
916 .. deprecated:: 8.8
915 Use :any:`evaluation` instead.
917 Use :any:`evaluation` and :any:`auto_close_dict_keys` instead.
916
918
917 As of IPython 8.8 proxy for ``evaluation = 'unsafe'`` when set to ``True``,
919 Whent enabled in IPython 8.8+ activates following settings for compatibility:
918 and for ``'forbidden'`` when set to ``False``.
920 - ``evaluation = 'unsafe'``
921 - ``auto_close_dict_keys = True``
919 """,
922 """,
920 ).tag(config=True)
923 ).tag(config=True)
921
924
@@ -957,6 +960,11 b' class Completer(Configurable):'
957 "Includes completion of latex commands, unicode names, and expanding "
960 "Includes completion of latex commands, unicode names, and expanding "
958 "unicode characters back to latex commands.").tag(config=True)
961 "unicode characters back to latex commands.").tag(config=True)
959
962
963 auto_close_dict_keys = Bool(
964 False,
965 help="""Enable auto-closing dictionary keys."""
966 ).tag(config=True)
967
960 def __init__(self, namespace=None, global_namespace=None, **kwargs):
968 def __init__(self, namespace=None, global_namespace=None, **kwargs):
961 """Create a new completer for the command line.
969 """Create a new completer for the command line.
962
970
@@ -1119,8 +1127,80 b' def get__all__entries(obj):'
1119 return [w for w in words if isinstance(w, str)]
1127 return [w for w in words if isinstance(w, str)]
1120
1128
1121
1129
1122 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]], prefix: str, delims: str,
1130 class DictKeyState(enum.Flag):
1123 extra_prefix: Optional[Tuple[Union[str, bytes], ...]]=None) -> Tuple[str, int, List[str]]:
1131 """Represent state of the key match in context of other possible matches.
1132
1133 - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple.
1134 - given `d2 = {('a', 'b'): 1}`: `d2['a', '<tab>` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`.
1135 - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added.
1136 - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}`
1137 """
1138 BASELINE = 0
1139 END_OF_ITEM = enum.auto()
1140 END_OF_TUPLE = enum.auto()
1141 IN_TUPLE = enum.auto()
1142
1143
1144 def _parse_tokens(c):
1145 tokens = []
1146 token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__)
1147 while True:
1148 try:
1149 tokens.append(next(token_generator))
1150 except tokenize.TokenError:
1151 return tokens
1152 except StopIteration:
1153 return tokens
1154
1155
1156 def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]:
1157 """Match any valid Python numeric literal in a prefix of dictionary keys.
1158
1159 References:
1160 - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals
1161 - https://docs.python.org/3/library/tokenize.html
1162 """
1163 if prefix[-1].isspace():
1164 # if user typed a space we do not have anything to complete
1165 # even if there was a valid number token before
1166 return None
1167 tokens = _parse_tokens(prefix)
1168 rev_tokens = reversed(tokens)
1169 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1170 number = None
1171 for token in rev_tokens:
1172 if token.type in skip_over:
1173 continue
1174 if number is None:
1175 if token.type == tokenize.NUMBER:
1176 number = token.string
1177 continue
1178 else:
1179 # we did not match a number
1180 return None
1181 if token.type == tokenize.OP:
1182 if token.string == ',':
1183 break
1184 if token.string in {'+', '-'}:
1185 number = token.string + number
1186 else:
1187 return None
1188 return number
1189
1190
1191 _INT_FORMATS = {
1192 '0b': bin,
1193 '0o': oct,
1194 '0x': hex,
1195 }
1196
1197
1198 def match_dict_keys(
1199 keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]],
1200 prefix: str,
1201 delims: str,
1202 extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None
1203 ) -> Tuple[str, int, Dict[str, DictKeyState]]:
1124 """Used by dict_key_matches, matching the prefix to a list of keys
1204 """Used by dict_key_matches, matching the prefix to a list of keys
1125
1205
1126 Parameters
1206 Parameters
@@ -1140,16 +1220,21 b' def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]]'
1140 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1220 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1141 ``quote`` being the quote that need to be used to close current string.
1221 ``quote`` being the quote that need to be used to close current string.
1142 ``token_start`` the position where the replacement should start occurring,
1222 ``token_start`` the position where the replacement should start occurring,
1143 ``matches`` a list of replacement/completion
1223 ``matches`` a dictionary of replacement/completion keys on keys and values
1144
1224 indicating whether the state.
1145 """
1225 """
1146 prefix_tuple = extra_prefix if extra_prefix else ()
1226 prefix_tuple = extra_prefix if extra_prefix else ()
1147
1227
1148 Nprefix = len(prefix_tuple)
1228 prefix_tuple_size = sum([
1229 # for pandas, do not count slices as taking space
1230 not isinstance(k, slice)
1231 for k in prefix_tuple
1232 ])
1149 text_serializable_types = (str, bytes, int, float, slice)
1233 text_serializable_types = (str, bytes, int, float, slice)
1234
1150 def filter_prefix_tuple(key):
1235 def filter_prefix_tuple(key):
1151 # Reject too short keys
1236 # Reject too short keys
1152 if len(key) <= Nprefix:
1237 if len(key) <= prefix_tuple_size:
1153 return False
1238 return False
1154 # Reject keys which cannot be serialised to text
1239 # Reject keys which cannot be serialised to text
1155 for k in key:
1240 for k in key:
@@ -1162,28 +1247,58 b' def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]]'
1162 # All checks passed!
1247 # All checks passed!
1163 return True
1248 return True
1164
1249
1165 filtered_keys: List[Union[str, bytes, int, float, slice]] = []
1250 filtered_key_is_final: Dict[Union[str, bytes, int, float], DictKeyState] = defaultdict(lambda: DictKeyState.BASELINE)
1166
1167 def _add_to_filtered_keys(key):
1168 if isinstance(key, text_serializable_types):
1169 filtered_keys.append(key)
1170
1251
1171 for k in keys:
1252 for k in keys:
1253 # If at least one of the matches is not final, mark as undetermined.
1254 # This can happen with `d = {111: 'b', (111, 222): 'a'}` where
1255 # `111` appears final on first match but is not final on the second.
1256
1172 if isinstance(k, tuple):
1257 if isinstance(k, tuple):
1173 if filter_prefix_tuple(k):
1258 if filter_prefix_tuple(k):
1174 _add_to_filtered_keys(k[Nprefix])
1259 key_fragment = k[prefix_tuple_size]
1260 filtered_key_is_final[key_fragment] |= (
1261 DictKeyState.END_OF_TUPLE
1262 if len(k) == prefix_tuple_size + 1 else
1263 DictKeyState.IN_TUPLE
1264 )
1265 elif prefix_tuple_size > 0:
1266 # we are completing a tuple but this key is not a tuple,
1267 # so we should ignore it
1268 pass
1175 else:
1269 else:
1176 _add_to_filtered_keys(k)
1270 if isinstance(k, text_serializable_types):
1271 filtered_key_is_final[k] |= DictKeyState.END_OF_ITEM
1272
1273 filtered_keys = filtered_key_is_final.keys()
1177
1274
1178 if not prefix:
1275 if not prefix:
1179 return '', 0, [repr(k) for k in filtered_keys]
1276 return '', 0, {repr(k): v for k, v in filtered_key_is_final.items()}
1180 quote_match = re.search('["\']', prefix)
1277
1181 assert quote_match is not None # silence mypy
1278 quote_match = re.search('(?:"|\')', prefix)
1182 quote = quote_match.group()
1279 is_user_prefix_numeric = False
1183 try:
1280
1184 prefix_str = literal_eval(prefix + quote)
1281 if quote_match:
1185 except Exception:
1282 quote = quote_match.group()
1186 return '', 0, []
1283 valid_prefix = prefix + quote
1284 try:
1285 prefix_str = literal_eval(valid_prefix)
1286 except Exception:
1287 return '', 0, {}
1288 else:
1289 # If it does not look like a string, let's assume
1290 # we are dealing with a number or variable.
1291 number_match = _match_number_in_dict_key_prefix(prefix)
1292
1293 # We do not want the key matcher to suggest variable names so we yield:
1294 if number_match is None:
1295 # The alternative would be to assume that user forgort the quote
1296 # and if the substring matches, suggest adding it at the start.
1297 return '', 0, {}
1298
1299 prefix_str = number_match
1300 is_user_prefix_numeric = True
1301 quote = ''
1187
1302
1188 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1303 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1189 token_match = re.search(pattern, prefix, re.UNICODE)
1304 token_match = re.search(pattern, prefix, re.UNICODE)
@@ -1191,13 +1306,29 b' def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]]'
1191 token_start = token_match.start()
1306 token_start = token_match.start()
1192 token_prefix = token_match.group()
1307 token_prefix = token_match.group()
1193
1308
1194 matched: List[str] = []
1309 matched: Dict[str, DictKeyState] = {}
1310
1195 for key in filtered_keys:
1311 for key in filtered_keys:
1196 str_key = key if isinstance(key, (str, bytes)) else str(key)
1312 if isinstance(key, (int, float)):
1313 # User typed a number but this key is not a number.
1314 if not is_user_prefix_numeric:
1315 continue
1316 str_key = str(key)
1317 if isinstance(key, int):
1318 int_base = prefix_str[:2].lower()
1319 # if user typed integer using binary/oct/hex notation:
1320 if int_base in _INT_FORMATS:
1321 int_format = _INT_FORMATS[int_base]
1322 str_key = int_format(key)
1323 else:
1324 # User typed a string but this key is a number.
1325 if is_user_prefix_numeric:
1326 continue
1327 str_key = key
1197 try:
1328 try:
1198 if not str_key.startswith(prefix_str):
1329 if not str_key.startswith(prefix_str):
1199 continue
1330 continue
1200 except (AttributeError, TypeError, UnicodeError):
1331 except (AttributeError, TypeError, UnicodeError) as e:
1201 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1332 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1202 continue
1333 continue
1203
1334
@@ -1213,7 +1344,9 b' def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]]'
1213 rem_repr = rem_repr.replace('"', '\\"')
1344 rem_repr = rem_repr.replace('"', '\\"')
1214
1345
1215 # then reinsert prefix from start of token
1346 # then reinsert prefix from start of token
1216 matched.append('%s%s' % (token_prefix, rem_repr))
1347 match = '%s%s' % (token_prefix, rem_repr)
1348
1349 matched[match] = filtered_key_is_final[key]
1217 return quote, token_start, matched
1350 return quote, token_start, matched
1218
1351
1219
1352
@@ -1447,24 +1580,39 b' DICT_MATCHER_REGEX = re.compile(r"""(?x)'
1447 \s* # and optional whitespace
1580 \s* # and optional whitespace
1448 # Capture any number of serializable objects (e.g. "a", "b", 'c')
1581 # Capture any number of serializable objects (e.g. "a", "b", 'c')
1449 # and slices
1582 # and slices
1450 ((?:[uUbB]? # string prefix (r not handled)
1583 ((?:(?:
1451 (?:
1584 (?: # closed string
1452 '(?:[^']|(?<!\\)\\')*'
1585 [uUbB]? # string prefix (r not handled)
1453 |
1586 (?:
1454 "(?:[^"]|(?<!\\)\\")*"
1587 '(?:[^']|(?<!\\)\\')*'
1588 |
1589 "(?:[^"]|(?<!\\)\\")*"
1590 )
1591 )
1455 |
1592 |
1456 # capture integers and slices
1593 # capture integers and slices
1457 (?:[-+]?\d+)?(?::(?:[-+]?\d+)?){0,2}
1594 (?:[-+]?\d+)?(?::(?:[-+]?\d+)?){0,2}
1595 |
1596 # integer in bin/hex/oct notation
1597 0[bBxXoO]_?(?:\w|\d)+
1458 )
1598 )
1459 \s*,\s*
1599 \s*,\s*
1460 )*)
1600 )*)
1461 ([uUbB]? # string prefix (r not handled)
1601 ((?:
1462 (?: # unclosed string
1602 (?: # unclosed string
1463 '(?:[^']|(?<!\\)\\')*
1603 [uUbB]? # string prefix (r not handled)
1464 |
1604 (?:
1465 "(?:[^"]|(?<!\\)\\")*
1605 '(?:[^']|(?<!\\)\\')*
1606 |
1607 "(?:[^"]|(?<!\\)\\")*
1608 )
1609 )
1466 |
1610 |
1611 # unfinished integer
1467 (?:[-+]?\d+)
1612 (?:[-+]?\d+)
1613 |
1614 # integer in bin/hex/oct notation
1615 0[bBxXoO]_?(?:\w|\d)+
1468 )
1616 )
1469 )?
1617 )?
1470 $
1618 $
@@ -1473,7 +1621,7 b' $'
1473 def _convert_matcher_v1_result_to_v2(
1621 def _convert_matcher_v1_result_to_v2(
1474 matches: Sequence[str],
1622 matches: Sequence[str],
1475 type: str,
1623 type: str,
1476 fragment: str = None,
1624 fragment: Optional[str] = None,
1477 suppress_if_matches: bool = False,
1625 suppress_if_matches: bool = False,
1478 ) -> SimpleMatcherResult:
1626 ) -> SimpleMatcherResult:
1479 """Utility to help with transition"""
1627 """Utility to help with transition"""
@@ -1494,9 +1642,11 b' class IPCompleter(Completer):'
1494 """update the splitter and readline delims when greedy is changed"""
1642 """update the splitter and readline delims when greedy is changed"""
1495 if change['new']:
1643 if change['new']:
1496 self.evaluation = 'unsafe'
1644 self.evaluation = 'unsafe'
1645 self.auto_close_dict_keys = True
1497 self.splitter.delims = GREEDY_DELIMS
1646 self.splitter.delims = GREEDY_DELIMS
1498 else:
1647 else:
1499 self.evaluation = 'limitted'
1648 self.evaluation = 'limitted'
1649 self.auto_close_dict_keys = False
1500 self.splitter.delims = DELIMS
1650 self.splitter.delims = DELIMS
1501
1651
1502 dict_keys_only = Bool(
1652 dict_keys_only = Bool(
@@ -2294,7 +2444,7 b' class IPCompleter(Completer):'
2294 extra_prefix=tuple_prefix
2444 extra_prefix=tuple_prefix
2295 )
2445 )
2296 if not matches:
2446 if not matches:
2297 return matches
2447 return []
2298
2448
2299 # get the cursor position of
2449 # get the cursor position of
2300 # - the text being completed
2450 # - the text being completed
@@ -2313,26 +2463,55 b' class IPCompleter(Completer):'
2313 else:
2463 else:
2314 leading = text[text_start:completion_start]
2464 leading = text[text_start:completion_start]
2315
2465
2316 # the index of the `[` character
2317 bracket_idx = match.end(1)
2318
2319 # append closing quote and bracket as appropriate
2466 # append closing quote and bracket as appropriate
2320 # this is *not* appropriate if the opening quote or bracket is outside
2467 # this is *not* appropriate if the opening quote or bracket is outside
2321 # the text given to this method
2468 # the text given to this method, e.g. `d["""a\nt
2322 suf = ''
2469 can_close_quote = False
2323 continuation = self.line_buffer[len(self.text_until_cursor):]
2470 can_close_bracket = False
2324 if key_start > text_start and closing_quote:
2471
2325 # quotes were opened inside text, maybe close them
2472 continuation = self.line_buffer[len(self.text_until_cursor):].strip()
2326 if continuation.startswith(closing_quote):
2473
2327 continuation = continuation[len(closing_quote):]
2474 if continuation.startswith(closing_quote):
2328 else:
2475 # do not close if already closed, e.g. `d['a<tab>'`
2329 suf += closing_quote
2476 continuation = continuation[len(closing_quote):]
2330 if bracket_idx > text_start:
2477 else:
2331 # brackets were opened inside text, maybe close them
2478 can_close_quote = True
2332 if not continuation.startswith(']'):
2479
2333 suf += ']'
2480 continuation = continuation.strip()
2481
2482 # e.g. `pandas.DataFrame` has different tuple indexer behaviour,
2483 # handling it is out of scope, so let's avoid appending suffixes.
2484 has_known_tuple_handling = isinstance(obj, dict)
2334
2485
2335 return [leading + k + suf for k in matches]
2486 can_close_bracket = not continuation.startswith(']') and self.auto_close_dict_keys
2487 can_close_tuple_item = not continuation.startswith(',') and has_known_tuple_handling and self.auto_close_dict_keys
2488 can_close_quote = can_close_quote and self.auto_close_dict_keys
2489
2490 # fast path if closing qoute should be appended but not suffix is allowed
2491 if not can_close_quote and not can_close_bracket and closing_quote:
2492 return [leading + k for k in matches]
2493
2494 results = []
2495
2496 end_of_tuple_or_item = DictKeyState.END_OF_TUPLE | DictKeyState.END_OF_ITEM
2497
2498 for k, state_flag in matches.items():
2499 result = leading + k
2500 if can_close_quote and closing_quote:
2501 result += closing_quote
2502
2503 if state_flag == end_of_tuple_or_item:
2504 # We do not know which suffix to add,
2505 # e.g. both tuple item and string
2506 # match this item.
2507 pass
2508
2509 if state_flag in end_of_tuple_or_item and can_close_bracket:
2510 result += ']'
2511 if state_flag == DictKeyState.IN_TUPLE and can_close_tuple_item:
2512 result += ', '
2513 results.append(result)
2514 return results
2336
2515
2337 @context_matcher()
2516 @context_matcher()
2338 def unicode_name_matcher(self, context: CompletionContext):
2517 def unicode_name_matcher(self, context: CompletionContext):
@@ -1,11 +1,19 b''
1 from typing import Callable, Protocol, Set, Tuple, NamedTuple, Literal, Union
1 from typing import Callable, Set, Tuple, NamedTuple, Literal, Union, TYPE_CHECKING
2 import collections
2 import collections
3 import sys
3 import sys
4 import ast
4 import ast
5 import types
6 from functools import cached_property
5 from functools import cached_property
7 from dataclasses import dataclass, field
6 from dataclasses import dataclass, field
8
7
8 from IPython.utils.docs import GENERATING_DOCUMENTATION
9
10
11 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
12 from typing_extensions import Protocol
13 else:
14 # do not require on runtime
15 Protocol = object # requires Python >=3.8
16
9
17
10 class HasGetItem(Protocol):
18 class HasGetItem(Protocol):
11 def __getitem__(self, key) -> None: ...
19 def __getitem__(self, key) -> None: ...
@@ -266,20 +274,23 b' def eval_node(node: Union[ast.AST, None], context: EvaluationContext):'
266 Currently does not support evaluation of functions with arguments.
274 Currently does not support evaluation of functions with arguments.
267
275
268 Does not evaluate actions which always have side effects:
276 Does not evaluate actions which always have side effects:
269 - class definitions (`class sth: ...`)
277 - class definitions (``class sth: ...``)
270 - function definitions (`def sth: ...`)
278 - function definitions (``def sth: ...``)
271 - variable assignments (`x = 1`)
279 - variable assignments (``x = 1``)
272 - augumented assignments (`x += 1`)
280 - augumented assignments (``x += 1``)
273 - deletions (`del x`)
281 - deletions (``del x``)
274
282
275 Does not evaluate operations which do not return values:
283 Does not evaluate operations which do not return values:
276 - assertions (`assert x`)
284 - assertions (``assert x``)
277 - pass (`pass`)
285 - pass (``pass``)
278 - imports (`import x`)
286 - imports (``import x``)
279 - control flow
287 - control flow
280 - conditionals (`if x:`) except for terenary IfExp (`a if x else b`)
288 - conditionals (``if x:``) except for terenary IfExp (``a if x else b``)
281 - loops (`for` and `while`)
289 - loops (``for`` and `while``)
282 - exception handling
290 - exception handling
291
292 The purpose of this function is to guard against unwanted side-effects;
293 it does not give guarantees on protection from malicious code execution.
283 """
294 """
284 policy = EVALUATION_POLICIES[context.evaluation]
295 policy = EVALUATION_POLICIES[context.evaluation]
285 if node is None:
296 if node is None:
@@ -24,6 +24,7 b' from IPython.core.completer import ('
24 provisionalcompleter,
24 provisionalcompleter,
25 match_dict_keys,
25 match_dict_keys,
26 _deduplicate_completions,
26 _deduplicate_completions,
27 _match_number_in_dict_key_prefix,
27 completion_matcher,
28 completion_matcher,
28 SimpleCompletion,
29 SimpleCompletion,
29 CompletionContext,
30 CompletionContext,
@@ -181,7 +182,6 b' def check_line_split(splitter, test_specs):'
181 out = splitter.split_line(line, cursor_pos)
182 out = splitter.split_line(line, cursor_pos)
182 assert out == split
183 assert out == split
183
184
184
185 def test_line_split():
185 def test_line_split():
186 """Basic line splitter test with default specs."""
186 """Basic line splitter test with default specs."""
187 sp = completer.CompletionSplitter()
187 sp = completer.CompletionSplitter()
@@ -852,16 +852,37 b' class TestCompleter(unittest.TestCase):'
852 """
852 """
853 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
853 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
854
854
855 def match(*args, **kwargs):
856 quote, offset, matches = match_dict_keys(*args, **kwargs)
857 return quote, offset, list(matches)
858
855 keys = ["foo", b"far"]
859 keys = ["foo", b"far"]
856 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
860 assert match(keys, "b'", delims=delims) == ("'", 2, ["far"])
857 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
861 assert match(keys, "b'f", delims=delims) == ("'", 2, ["far"])
858 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
862 assert match(keys, 'b"', delims=delims) == ('"', 2, ["far"])
859 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
863 assert match(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
860
864
861 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
865 assert match(keys, "'", delims=delims) == ("'", 1, ["foo"])
862 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
866 assert match(keys, "'f", delims=delims) == ("'", 1, ["foo"])
863 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
867 assert match(keys, '"', delims=delims) == ('"', 1, ["foo"])
864 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
868 assert match(keys, '"f', delims=delims) == ('"', 1, ["foo"])
869
870 # Completion on first item of tuple
871 keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, 'test')]
872 assert match(keys, "'f", delims=delims) == ("'", 1, ["foo"])
873 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
874
875 # Completion on numbers
876 keys = [
877 0xdeadbeef, # 3735928559
878 1111, 1234, "1999",
879 0b10101, # 21
880 22
881 ]
882 assert match(keys, "0xdead", delims=delims) == ("", 0, ["0xdeadbeef"])
883 assert match(keys, "1", delims=delims) == ("", 0, ["1111", "1234"])
884 assert match(keys, "2", delims=delims) == ("", 0, ["21", "22"])
885 assert match(keys, "0b101", delims=delims) == ("", 0, ['0b10101', '0b10110'])
865
886
866 def test_match_dict_keys_tuple(self):
887 def test_match_dict_keys_tuple(self):
867 """
888 """
@@ -872,30 +893,85 b' class TestCompleter(unittest.TestCase):'
872
893
873 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
894 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
874
895
896 def match(*args, **kwargs):
897 quote, offset, matches = match_dict_keys(*args, **kwargs)
898 return quote, offset, list(matches)
899
875 # Completion on first key == "foo"
900 # Completion on first key == "foo"
876 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
901 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
877 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
902 assert match(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
878 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
903 assert match(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
879 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
904 assert match(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
880 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
905 assert match(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
881 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
906 assert match(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
882 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
907 assert match(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
883 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
908 assert match(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
884
909
885 # No Completion
910 # No Completion
886 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
911 assert match(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
887 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
912 assert match(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
888
913
889 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
914 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
890 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
915 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2"])
891 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
916 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
892 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
917 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
893 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
918 assert match(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
919
920 keys = [("foo", 1111), ("foo", "2222"), (3333, "bar"), (3333, 4444)]
921 assert match(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["2222"])
922 assert match(keys, "", delims=delims, extra_prefix=("foo",)) == ("", 0, ["1111", "'2222'"])
923 assert match(keys, "'", delims=delims, extra_prefix=(3333,)) == ("'", 1, ["bar"])
924 assert match(keys, "", delims=delims, extra_prefix=(3333,)) == ("", 0, ["'bar'", "4444"])
925 assert match(keys, "'", delims=delims, extra_prefix=("3333",)) == ("'", 1, [])
926 assert match(keys, "33", delims=delims) == ("", 0, ["3333"])
927
928 def test_dict_key_completion_closures(self):
929 ip = get_ipython()
930 complete = ip.Completer.complete
931 ip.Completer.auto_close_dict_keys = True
894
932
895 keys = [("foo", 1111), ("foo", 2222), (3333, "bar"), (3333, 'test')]
933 ip.user_ns["d"] = {
896 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["1111", "2222"])
934 # tuple only
897 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=(3333,)) == ("'", 1, ["bar", "test"])
935 ('aa', 11): None,
898 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("3333",)) == ("'", 1, [])
936 # tuple and non-tuple
937 ('bb', 22): None,
938 'bb': None,
939 # non-tuple only
940 'cc': None,
941 # numeric tuple only
942 (77, 'x'): None,
943 # numeric tuple and non-tuple
944 (88, 'y'): None,
945 88: None,
946 # numeric non-tuple only
947 99: None,
948 }
949
950 _, matches = complete(line_buffer="d[")
951 # should append `, ` if matches a tuple only
952 self.assertIn("'aa', ", matches)
953 # should not append anything if matches a tuple and an item
954 self.assertIn("'bb'", matches)
955 # should append `]` if matches and item only
956 self.assertIn("'cc']", matches)
957
958 # should append `, ` if matches a tuple only
959 self.assertIn("77, ", matches)
960 # should not append anything if matches a tuple and an item
961 self.assertIn("88", matches)
962 # should append `]` if matches and item only
963 self.assertIn("99]", matches)
964
965 _, matches = complete(line_buffer="d['aa', ")
966 # should restrict matches to those matching tuple prefix
967 self.assertIn("11]", matches)
968 self.assertNotIn("'bb'", matches)
969 self.assertNotIn("'bb', ", matches)
970 self.assertNotIn("'bb']", matches)
971 self.assertNotIn("'cc'", matches)
972 self.assertNotIn("'cc', ", matches)
973 self.assertNotIn("'cc']", matches)
974 ip.Completer.auto_close_dict_keys = False
899
975
900 def test_dict_key_completion_string(self):
976 def test_dict_key_completion_string(self):
901 """Test dictionary key completion for string keys"""
977 """Test dictionary key completion for string keys"""
@@ -1052,6 +1128,35 b' class TestCompleter(unittest.TestCase):'
1052 self.assertNotIn("foo", matches)
1128 self.assertNotIn("foo", matches)
1053 self.assertNotIn("bar", matches)
1129 self.assertNotIn("bar", matches)
1054
1130
1131 def test_dict_key_completion_numbers(self):
1132 ip = get_ipython()
1133 complete = ip.Completer.complete
1134
1135 ip.user_ns["d"] = {
1136 0xdeadbeef: None, # 3735928559
1137 1111: None,
1138 1234: None,
1139 "1999": None,
1140 0b10101: None, # 21
1141 22: None
1142 }
1143 _, matches = complete(line_buffer="d[1")
1144 self.assertIn("1111", matches)
1145 self.assertIn("1234", matches)
1146 self.assertNotIn("1999", matches)
1147 self.assertNotIn("'1999'", matches)
1148
1149 _, matches = complete(line_buffer="d[0xdead")
1150 self.assertIn("0xdeadbeef", matches)
1151
1152 _, matches = complete(line_buffer="d[2")
1153 self.assertIn("21", matches)
1154 self.assertIn("22", matches)
1155
1156 _, matches = complete(line_buffer="d[0b101")
1157 self.assertIn("0b10101", matches)
1158 self.assertIn("0b10110", matches)
1159
1055 def test_dict_key_completion_contexts(self):
1160 def test_dict_key_completion_contexts(self):
1056 """Test expression contexts in which dict key completion occurs"""
1161 """Test expression contexts in which dict key completion occurs"""
1057 ip = get_ipython()
1162 ip = get_ipython()
@@ -1545,3 +1650,35 b' class TestCompleter(unittest.TestCase):'
1545 _(["completion_b"])
1650 _(["completion_b"])
1546 a_matcher.matcher_priority = 3
1651 a_matcher.matcher_priority = 3
1547 _(["completion_a"])
1652 _(["completion_a"])
1653
1654
1655 @pytest.mark.parametrize(
1656 'input, expected',
1657 [
1658 ['1.234', '1.234'],
1659 # should match signed numbers
1660 ['+1', '+1'],
1661 ['-1', '-1'],
1662 ['-1.0', '-1.0'],
1663 ['-1.', '-1.'],
1664 ['+1.', '+1.'],
1665 ['.1', '.1'],
1666 # should not match non-numbers
1667 ['1..', None],
1668 ['..', None],
1669 ['.1.', None],
1670 # should match after comma
1671 [',1', '1'],
1672 [', 1', '1'],
1673 [', .1', '.1'],
1674 [', +.1', '+.1'],
1675 # should not match after trailing spaces
1676 ['.1 ', None],
1677 # some complex cases
1678 ['0b_0011_1111_0100_1110', '0b_0011_1111_0100_1110'],
1679 ['0xdeadbeef', '0xdeadbeef'],
1680 ['0b_1110_0101', '0b_1110_0101']
1681 ]
1682 )
1683 def test_match_numeric_literal_for_dict_key(input, expected):
1684 assert _match_number_in_dict_key_prefix(input) == expected
General Comments 0
You need to be logged in to leave comments. Login now