##// END OF EJS Templates
Remove set-next input when triggering help....
Matthias Bussonnier -
Show More
@@ -1,538 +1,536 b''
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 This includes the machinery to recognise and transform ``%magic`` commands,
5 This includes the machinery to recognise and transform ``%magic`` commands,
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
7 """
7 """
8 import abc
8 import abc
9 import functools
9 import functools
10 import re
10 import re
11 import tokenize
11 import tokenize
12 from tokenize import generate_tokens, untokenize, TokenError
12 from tokenize import generate_tokens, untokenize, TokenError
13 from io import StringIO
13 from io import StringIO
14
14
15 from IPython.core.splitinput import LineInfo
15 from IPython.core.splitinput import LineInfo
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Globals
18 # Globals
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # The escape sequences that define the syntax transformations IPython will
21 # The escape sequences that define the syntax transformations IPython will
22 # apply to user input. These can NOT be just changed here: many regular
22 # apply to user input. These can NOT be just changed here: many regular
23 # expressions and other parts of the code may use their hardcoded values, and
23 # expressions and other parts of the code may use their hardcoded values, and
24 # for all intents and purposes they constitute the 'IPython syntax', so they
24 # for all intents and purposes they constitute the 'IPython syntax', so they
25 # should be considered fixed.
25 # should be considered fixed.
26
26
27 ESC_SHELL = '!' # Send line to underlying system shell
27 ESC_SHELL = '!' # Send line to underlying system shell
28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
29 ESC_HELP = '?' # Find information about object
29 ESC_HELP = '?' # Find information about object
30 ESC_HELP2 = '??' # Find extra-detailed information about object
30 ESC_HELP2 = '??' # Find extra-detailed information about object
31 ESC_MAGIC = '%' # Call magic function
31 ESC_MAGIC = '%' # Call magic function
32 ESC_MAGIC2 = '%%' # Call cell-magic function
32 ESC_MAGIC2 = '%%' # Call cell-magic function
33 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
33 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
36
36
37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
40
40
41
41
42 class InputTransformer(metaclass=abc.ABCMeta):
42 class InputTransformer(metaclass=abc.ABCMeta):
43 """Abstract base class for line-based input transformers."""
43 """Abstract base class for line-based input transformers."""
44
44
45 @abc.abstractmethod
45 @abc.abstractmethod
46 def push(self, line):
46 def push(self, line):
47 """Send a line of input to the transformer, returning the transformed
47 """Send a line of input to the transformer, returning the transformed
48 input or None if the transformer is waiting for more input.
48 input or None if the transformer is waiting for more input.
49
49
50 Must be overridden by subclasses.
50 Must be overridden by subclasses.
51
51
52 Implementations may raise ``SyntaxError`` if the input is invalid. No
52 Implementations may raise ``SyntaxError`` if the input is invalid. No
53 other exceptions may be raised.
53 other exceptions may be raised.
54 """
54 """
55 pass
55 pass
56
56
57 @abc.abstractmethod
57 @abc.abstractmethod
58 def reset(self):
58 def reset(self):
59 """Return, transformed any lines that the transformer has accumulated,
59 """Return, transformed any lines that the transformer has accumulated,
60 and reset its internal state.
60 and reset its internal state.
61
61
62 Must be overridden by subclasses.
62 Must be overridden by subclasses.
63 """
63 """
64 pass
64 pass
65
65
66 @classmethod
66 @classmethod
67 def wrap(cls, func):
67 def wrap(cls, func):
68 """Can be used by subclasses as a decorator, to return a factory that
68 """Can be used by subclasses as a decorator, to return a factory that
69 will allow instantiation with the decorated object.
69 will allow instantiation with the decorated object.
70 """
70 """
71 @functools.wraps(func)
71 @functools.wraps(func)
72 def transformer_factory(**kwargs):
72 def transformer_factory(**kwargs):
73 return cls(func, **kwargs)
73 return cls(func, **kwargs)
74
74
75 return transformer_factory
75 return transformer_factory
76
76
77 class StatelessInputTransformer(InputTransformer):
77 class StatelessInputTransformer(InputTransformer):
78 """Wrapper for a stateless input transformer implemented as a function."""
78 """Wrapper for a stateless input transformer implemented as a function."""
79 def __init__(self, func):
79 def __init__(self, func):
80 self.func = func
80 self.func = func
81
81
82 def __repr__(self):
82 def __repr__(self):
83 return "StatelessInputTransformer(func={0!r})".format(self.func)
83 return "StatelessInputTransformer(func={0!r})".format(self.func)
84
84
85 def push(self, line):
85 def push(self, line):
86 """Send a line of input to the transformer, returning the
86 """Send a line of input to the transformer, returning the
87 transformed input."""
87 transformed input."""
88 return self.func(line)
88 return self.func(line)
89
89
90 def reset(self):
90 def reset(self):
91 """No-op - exists for compatibility."""
91 """No-op - exists for compatibility."""
92 pass
92 pass
93
93
94 class CoroutineInputTransformer(InputTransformer):
94 class CoroutineInputTransformer(InputTransformer):
95 """Wrapper for an input transformer implemented as a coroutine."""
95 """Wrapper for an input transformer implemented as a coroutine."""
96 def __init__(self, coro, **kwargs):
96 def __init__(self, coro, **kwargs):
97 # Prime it
97 # Prime it
98 self.coro = coro(**kwargs)
98 self.coro = coro(**kwargs)
99 next(self.coro)
99 next(self.coro)
100
100
101 def __repr__(self):
101 def __repr__(self):
102 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
102 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
103
103
104 def push(self, line):
104 def push(self, line):
105 """Send a line of input to the transformer, returning the
105 """Send a line of input to the transformer, returning the
106 transformed input or None if the transformer is waiting for more
106 transformed input or None if the transformer is waiting for more
107 input.
107 input.
108 """
108 """
109 return self.coro.send(line)
109 return self.coro.send(line)
110
110
111 def reset(self):
111 def reset(self):
112 """Return, transformed any lines that the transformer has
112 """Return, transformed any lines that the transformer has
113 accumulated, and reset its internal state.
113 accumulated, and reset its internal state.
114 """
114 """
115 return self.coro.send(None)
115 return self.coro.send(None)
116
116
117 class TokenInputTransformer(InputTransformer):
117 class TokenInputTransformer(InputTransformer):
118 """Wrapper for a token-based input transformer.
118 """Wrapper for a token-based input transformer.
119
119
120 func should accept a list of tokens (5-tuples, see tokenize docs), and
120 func should accept a list of tokens (5-tuples, see tokenize docs), and
121 return an iterable which can be passed to tokenize.untokenize().
121 return an iterable which can be passed to tokenize.untokenize().
122 """
122 """
123 def __init__(self, func):
123 def __init__(self, func):
124 self.func = func
124 self.func = func
125 self.buf = []
125 self.buf = []
126 self.reset_tokenizer()
126 self.reset_tokenizer()
127
127
128 def reset_tokenizer(self):
128 def reset_tokenizer(self):
129 it = iter(self.buf)
129 it = iter(self.buf)
130 self.tokenizer = generate_tokens(it.__next__)
130 self.tokenizer = generate_tokens(it.__next__)
131
131
132 def push(self, line):
132 def push(self, line):
133 self.buf.append(line + '\n')
133 self.buf.append(line + '\n')
134 if all(l.isspace() for l in self.buf):
134 if all(l.isspace() for l in self.buf):
135 return self.reset()
135 return self.reset()
136
136
137 tokens = []
137 tokens = []
138 stop_at_NL = False
138 stop_at_NL = False
139 try:
139 try:
140 for intok in self.tokenizer:
140 for intok in self.tokenizer:
141 tokens.append(intok)
141 tokens.append(intok)
142 t = intok[0]
142 t = intok[0]
143 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
143 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
144 # Stop before we try to pull a line we don't have yet
144 # Stop before we try to pull a line we don't have yet
145 break
145 break
146 elif t == tokenize.ERRORTOKEN:
146 elif t == tokenize.ERRORTOKEN:
147 stop_at_NL = True
147 stop_at_NL = True
148 except TokenError:
148 except TokenError:
149 # Multi-line statement - stop and try again with the next line
149 # Multi-line statement - stop and try again with the next line
150 self.reset_tokenizer()
150 self.reset_tokenizer()
151 return None
151 return None
152
152
153 return self.output(tokens)
153 return self.output(tokens)
154
154
155 def output(self, tokens):
155 def output(self, tokens):
156 self.buf.clear()
156 self.buf.clear()
157 self.reset_tokenizer()
157 self.reset_tokenizer()
158 return untokenize(self.func(tokens)).rstrip('\n')
158 return untokenize(self.func(tokens)).rstrip('\n')
159
159
160 def reset(self):
160 def reset(self):
161 l = ''.join(self.buf)
161 l = ''.join(self.buf)
162 self.buf.clear()
162 self.buf.clear()
163 self.reset_tokenizer()
163 self.reset_tokenizer()
164 if l:
164 if l:
165 return l.rstrip('\n')
165 return l.rstrip('\n')
166
166
167 class assemble_python_lines(TokenInputTransformer):
167 class assemble_python_lines(TokenInputTransformer):
168 def __init__(self):
168 def __init__(self):
169 super(assemble_python_lines, self).__init__(None)
169 super(assemble_python_lines, self).__init__(None)
170
170
171 def output(self, tokens):
171 def output(self, tokens):
172 return self.reset()
172 return self.reset()
173
173
174 @CoroutineInputTransformer.wrap
174 @CoroutineInputTransformer.wrap
175 def assemble_logical_lines():
175 def assemble_logical_lines():
176 r"""Join lines following explicit line continuations (\)"""
176 r"""Join lines following explicit line continuations (\)"""
177 line = ''
177 line = ''
178 while True:
178 while True:
179 line = (yield line)
179 line = (yield line)
180 if not line or line.isspace():
180 if not line or line.isspace():
181 continue
181 continue
182
182
183 parts = []
183 parts = []
184 while line is not None:
184 while line is not None:
185 if line.endswith('\\') and (not has_comment(line)):
185 if line.endswith('\\') and (not has_comment(line)):
186 parts.append(line[:-1])
186 parts.append(line[:-1])
187 line = (yield None) # Get another line
187 line = (yield None) # Get another line
188 else:
188 else:
189 parts.append(line)
189 parts.append(line)
190 break
190 break
191
191
192 # Output
192 # Output
193 line = ''.join(parts)
193 line = ''.join(parts)
194
194
195 # Utilities
195 # Utilities
196 def _make_help_call(target, esc, lspace, next_input=None):
196 def _make_help_call(target, esc, lspace):
197 """Prepares a pinfo(2)/psearch call from a target name and the escape
197 """Prepares a pinfo(2)/psearch call from a target name and the escape
198 (i.e. ? or ??)"""
198 (i.e. ? or ??)"""
199 method = 'pinfo2' if esc == '??' \
199 method = 'pinfo2' if esc == '??' \
200 else 'psearch' if '*' in target \
200 else 'psearch' if '*' in target \
201 else 'pinfo'
201 else 'pinfo'
202 arg = " ".join([method, target])
202 arg = " ".join([method, target])
203 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
203 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
204 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
204 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
205 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
205 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
206 if next_input is None:
206 return "%sget_ipython().run_line_magic(%r, %r)" % (
207 return '%sget_ipython().run_line_magic(%r, %r)' % (lspace, t_magic_name, t_magic_arg_s)
207 lspace,
208 else:
208 t_magic_name,
209 return '%sget_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
209 t_magic_arg_s,
210 (lspace, next_input, t_magic_name, t_magic_arg_s)
210 )
211
211
212
212 # These define the transformations for the different escape characters.
213 # These define the transformations for the different escape characters.
213 def _tr_system(line_info):
214 def _tr_system(line_info):
214 "Translate lines escaped with: !"
215 "Translate lines escaped with: !"
215 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
216 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
216 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
217 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
217
218
218 def _tr_system2(line_info):
219 def _tr_system2(line_info):
219 "Translate lines escaped with: !!"
220 "Translate lines escaped with: !!"
220 cmd = line_info.line.lstrip()[2:]
221 cmd = line_info.line.lstrip()[2:]
221 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
222 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
222
223
223 def _tr_help(line_info):
224 def _tr_help(line_info):
224 "Translate lines escaped with: ?/??"
225 "Translate lines escaped with: ?/??"
225 # A naked help line should just fire the intro help screen
226 # A naked help line should just fire the intro help screen
226 if not line_info.line[1:]:
227 if not line_info.line[1:]:
227 return 'get_ipython().show_usage()'
228 return 'get_ipython().show_usage()'
228
229
229 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
230 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
230
231
231 def _tr_magic(line_info):
232 def _tr_magic(line_info):
232 "Translate lines escaped with: %"
233 "Translate lines escaped with: %"
233 tpl = '%sget_ipython().run_line_magic(%r, %r)'
234 tpl = '%sget_ipython().run_line_magic(%r, %r)'
234 if line_info.line.startswith(ESC_MAGIC2):
235 if line_info.line.startswith(ESC_MAGIC2):
235 return line_info.line
236 return line_info.line
236 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
237 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
237 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
238 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
238 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
239 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
239 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
240 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
240 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
241 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
241
242
242 def _tr_quote(line_info):
243 def _tr_quote(line_info):
243 "Translate lines escaped with: ,"
244 "Translate lines escaped with: ,"
244 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
245 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
245 '", "'.join(line_info.the_rest.split()) )
246 '", "'.join(line_info.the_rest.split()) )
246
247
247 def _tr_quote2(line_info):
248 def _tr_quote2(line_info):
248 "Translate lines escaped with: ;"
249 "Translate lines escaped with: ;"
249 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
250 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
250 line_info.the_rest)
251 line_info.the_rest)
251
252
252 def _tr_paren(line_info):
253 def _tr_paren(line_info):
253 "Translate lines escaped with: /"
254 "Translate lines escaped with: /"
254 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
255 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
255 ", ".join(line_info.the_rest.split()))
256 ", ".join(line_info.the_rest.split()))
256
257
257 tr = { ESC_SHELL : _tr_system,
258 tr = { ESC_SHELL : _tr_system,
258 ESC_SH_CAP : _tr_system2,
259 ESC_SH_CAP : _tr_system2,
259 ESC_HELP : _tr_help,
260 ESC_HELP : _tr_help,
260 ESC_HELP2 : _tr_help,
261 ESC_HELP2 : _tr_help,
261 ESC_MAGIC : _tr_magic,
262 ESC_MAGIC : _tr_magic,
262 ESC_QUOTE : _tr_quote,
263 ESC_QUOTE : _tr_quote,
263 ESC_QUOTE2 : _tr_quote2,
264 ESC_QUOTE2 : _tr_quote2,
264 ESC_PAREN : _tr_paren }
265 ESC_PAREN : _tr_paren }
265
266
266 @StatelessInputTransformer.wrap
267 @StatelessInputTransformer.wrap
267 def escaped_commands(line):
268 def escaped_commands(line):
268 """Transform escaped commands - %magic, !system, ?help + various autocalls.
269 """Transform escaped commands - %magic, !system, ?help + various autocalls.
269 """
270 """
270 if not line or line.isspace():
271 if not line or line.isspace():
271 return line
272 return line
272 lineinf = LineInfo(line)
273 lineinf = LineInfo(line)
273 if lineinf.esc not in tr:
274 if lineinf.esc not in tr:
274 return line
275 return line
275
276
276 return tr[lineinf.esc](lineinf)
277 return tr[lineinf.esc](lineinf)
277
278
278 _initial_space_re = re.compile(r'\s*')
279 _initial_space_re = re.compile(r'\s*')
279
280
280 _help_end_re = re.compile(r"""(%{0,2}
281 _help_end_re = re.compile(r"""(%{0,2}
281 (?!\d)[\w*]+ # Variable name
282 (?!\d)[\w*]+ # Variable name
282 (\.(?!\d)[\w*]+)* # .etc.etc
283 (\.(?!\d)[\w*]+)* # .etc.etc
283 )
284 )
284 (\?\??)$ # ? or ??
285 (\?\??)$ # ? or ??
285 """,
286 """,
286 re.VERBOSE)
287 re.VERBOSE)
287
288
288 # Extra pseudotokens for multiline strings and data structures
289 # Extra pseudotokens for multiline strings and data structures
289 _MULTILINE_STRING = object()
290 _MULTILINE_STRING = object()
290 _MULTILINE_STRUCTURE = object()
291 _MULTILINE_STRUCTURE = object()
291
292
292 def _line_tokens(line):
293 def _line_tokens(line):
293 """Helper for has_comment and ends_in_comment_or_string."""
294 """Helper for has_comment and ends_in_comment_or_string."""
294 readline = StringIO(line).readline
295 readline = StringIO(line).readline
295 toktypes = set()
296 toktypes = set()
296 try:
297 try:
297 for t in generate_tokens(readline):
298 for t in generate_tokens(readline):
298 toktypes.add(t[0])
299 toktypes.add(t[0])
299 except TokenError as e:
300 except TokenError as e:
300 # There are only two cases where a TokenError is raised.
301 # There are only two cases where a TokenError is raised.
301 if 'multi-line string' in e.args[0]:
302 if 'multi-line string' in e.args[0]:
302 toktypes.add(_MULTILINE_STRING)
303 toktypes.add(_MULTILINE_STRING)
303 else:
304 else:
304 toktypes.add(_MULTILINE_STRUCTURE)
305 toktypes.add(_MULTILINE_STRUCTURE)
305 return toktypes
306 return toktypes
306
307
307 def has_comment(src):
308 def has_comment(src):
308 """Indicate whether an input line has (i.e. ends in, or is) a comment.
309 """Indicate whether an input line has (i.e. ends in, or is) a comment.
309
310
310 This uses tokenize, so it can distinguish comments from # inside strings.
311 This uses tokenize, so it can distinguish comments from # inside strings.
311
312
312 Parameters
313 Parameters
313 ----------
314 ----------
314 src : string
315 src : string
315 A single line input string.
316 A single line input string.
316
317
317 Returns
318 Returns
318 -------
319 -------
319 comment : bool
320 comment : bool
320 True if source has a comment.
321 True if source has a comment.
321 """
322 """
322 return (tokenize.COMMENT in _line_tokens(src))
323 return (tokenize.COMMENT in _line_tokens(src))
323
324
324 def ends_in_comment_or_string(src):
325 def ends_in_comment_or_string(src):
325 """Indicates whether or not an input line ends in a comment or within
326 """Indicates whether or not an input line ends in a comment or within
326 a multiline string.
327 a multiline string.
327
328
328 Parameters
329 Parameters
329 ----------
330 ----------
330 src : string
331 src : string
331 A single line input string.
332 A single line input string.
332
333
333 Returns
334 Returns
334 -------
335 -------
335 comment : bool
336 comment : bool
336 True if source ends in a comment or multiline string.
337 True if source ends in a comment or multiline string.
337 """
338 """
338 toktypes = _line_tokens(src)
339 toktypes = _line_tokens(src)
339 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
340 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
340
341
341
342
342 @StatelessInputTransformer.wrap
343 @StatelessInputTransformer.wrap
343 def help_end(line):
344 def help_end(line):
344 """Translate lines with ?/?? at the end"""
345 """Translate lines with ?/?? at the end"""
345 m = _help_end_re.search(line)
346 m = _help_end_re.search(line)
346 if m is None or ends_in_comment_or_string(line):
347 if m is None or ends_in_comment_or_string(line):
347 return line
348 return line
348 target = m.group(1)
349 target = m.group(1)
349 esc = m.group(3)
350 esc = m.group(3)
350 lspace = _initial_space_re.match(line).group(0)
351 lspace = _initial_space_re.match(line).group(0)
351
352
352 # If we're mid-command, put it back on the next prompt for the user.
353 return _make_help_call(target, esc, lspace)
353 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
354
355 return _make_help_call(target, esc, lspace, next_input)
356
354
357
355
358 @CoroutineInputTransformer.wrap
356 @CoroutineInputTransformer.wrap
359 def cellmagic(end_on_blank_line=False):
357 def cellmagic(end_on_blank_line=False):
360 """Captures & transforms cell magics.
358 """Captures & transforms cell magics.
361
359
362 After a cell magic is started, this stores up any lines it gets until it is
360 After a cell magic is started, this stores up any lines it gets until it is
363 reset (sent None).
361 reset (sent None).
364 """
362 """
365 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
363 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
366 cellmagic_help_re = re.compile(r'%%\w+\?')
364 cellmagic_help_re = re.compile(r'%%\w+\?')
367 line = ''
365 line = ''
368 while True:
366 while True:
369 line = (yield line)
367 line = (yield line)
370 # consume leading empty lines
368 # consume leading empty lines
371 while not line:
369 while not line:
372 line = (yield line)
370 line = (yield line)
373
371
374 if not line.startswith(ESC_MAGIC2):
372 if not line.startswith(ESC_MAGIC2):
375 # This isn't a cell magic, idle waiting for reset then start over
373 # This isn't a cell magic, idle waiting for reset then start over
376 while line is not None:
374 while line is not None:
377 line = (yield line)
375 line = (yield line)
378 continue
376 continue
379
377
380 if cellmagic_help_re.match(line):
378 if cellmagic_help_re.match(line):
381 # This case will be handled by help_end
379 # This case will be handled by help_end
382 continue
380 continue
383
381
384 first = line
382 first = line
385 body = []
383 body = []
386 line = (yield None)
384 line = (yield None)
387 while (line is not None) and \
385 while (line is not None) and \
388 ((line.strip() != '') or not end_on_blank_line):
386 ((line.strip() != '') or not end_on_blank_line):
389 body.append(line)
387 body.append(line)
390 line = (yield None)
388 line = (yield None)
391
389
392 # Output
390 # Output
393 magic_name, _, first = first.partition(' ')
391 magic_name, _, first = first.partition(' ')
394 magic_name = magic_name.lstrip(ESC_MAGIC2)
392 magic_name = magic_name.lstrip(ESC_MAGIC2)
395 line = tpl % (magic_name, first, u'\n'.join(body))
393 line = tpl % (magic_name, first, u'\n'.join(body))
396
394
397
395
398 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
396 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
399 """Remove matching input prompts from a block of input.
397 """Remove matching input prompts from a block of input.
400
398
401 Parameters
399 Parameters
402 ----------
400 ----------
403 prompt_re : regular expression
401 prompt_re : regular expression
404 A regular expression matching any input prompt (including continuation)
402 A regular expression matching any input prompt (including continuation)
405 initial_re : regular expression, optional
403 initial_re : regular expression, optional
406 A regular expression matching only the initial prompt, but not continuation.
404 A regular expression matching only the initial prompt, but not continuation.
407 If no initial expression is given, prompt_re will be used everywhere.
405 If no initial expression is given, prompt_re will be used everywhere.
408 Used mainly for plain Python prompts, where the continuation prompt
406 Used mainly for plain Python prompts, where the continuation prompt
409 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
407 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
410
408
411 Notes
409 Notes
412 -----
410 -----
413 If `initial_re` and `prompt_re differ`,
411 If `initial_re` and `prompt_re differ`,
414 only `initial_re` will be tested against the first line.
412 only `initial_re` will be tested against the first line.
415 If any prompt is found on the first two lines,
413 If any prompt is found on the first two lines,
416 prompts will be stripped from the rest of the block.
414 prompts will be stripped from the rest of the block.
417 """
415 """
418 if initial_re is None:
416 if initial_re is None:
419 initial_re = prompt_re
417 initial_re = prompt_re
420 line = ''
418 line = ''
421 while True:
419 while True:
422 line = (yield line)
420 line = (yield line)
423
421
424 # First line of cell
422 # First line of cell
425 if line is None:
423 if line is None:
426 continue
424 continue
427 out, n1 = initial_re.subn('', line, count=1)
425 out, n1 = initial_re.subn('', line, count=1)
428 if turnoff_re and not n1:
426 if turnoff_re and not n1:
429 if turnoff_re.match(line):
427 if turnoff_re.match(line):
430 # We're in e.g. a cell magic; disable this transformer for
428 # We're in e.g. a cell magic; disable this transformer for
431 # the rest of the cell.
429 # the rest of the cell.
432 while line is not None:
430 while line is not None:
433 line = (yield line)
431 line = (yield line)
434 continue
432 continue
435
433
436 line = (yield out)
434 line = (yield out)
437
435
438 if line is None:
436 if line is None:
439 continue
437 continue
440 # check for any prompt on the second line of the cell,
438 # check for any prompt on the second line of the cell,
441 # because people often copy from just after the first prompt,
439 # because people often copy from just after the first prompt,
442 # so we might not see it in the first line.
440 # so we might not see it in the first line.
443 out, n2 = prompt_re.subn('', line, count=1)
441 out, n2 = prompt_re.subn('', line, count=1)
444 line = (yield out)
442 line = (yield out)
445
443
446 if n1 or n2:
444 if n1 or n2:
447 # Found a prompt in the first two lines - check for it in
445 # Found a prompt in the first two lines - check for it in
448 # the rest of the cell as well.
446 # the rest of the cell as well.
449 while line is not None:
447 while line is not None:
450 line = (yield prompt_re.sub('', line, count=1))
448 line = (yield prompt_re.sub('', line, count=1))
451
449
452 else:
450 else:
453 # Prompts not in input - wait for reset
451 # Prompts not in input - wait for reset
454 while line is not None:
452 while line is not None:
455 line = (yield line)
453 line = (yield line)
456
454
457 @CoroutineInputTransformer.wrap
455 @CoroutineInputTransformer.wrap
458 def classic_prompt():
456 def classic_prompt():
459 """Strip the >>>/... prompts of the Python interactive shell."""
457 """Strip the >>>/... prompts of the Python interactive shell."""
460 # FIXME: non-capturing version (?:...) usable?
458 # FIXME: non-capturing version (?:...) usable?
461 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
459 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
462 initial_re = re.compile(r'^>>>( |$)')
460 initial_re = re.compile(r'^>>>( |$)')
463 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
461 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
464 turnoff_re = re.compile(r'^[%!]')
462 turnoff_re = re.compile(r'^[%!]')
465 return _strip_prompts(prompt_re, initial_re, turnoff_re)
463 return _strip_prompts(prompt_re, initial_re, turnoff_re)
466
464
467 @CoroutineInputTransformer.wrap
465 @CoroutineInputTransformer.wrap
468 def ipy_prompt():
466 def ipy_prompt():
469 """Strip IPython's In [1]:/...: prompts."""
467 """Strip IPython's In [1]:/...: prompts."""
470 # FIXME: non-capturing version (?:...) usable?
468 # FIXME: non-capturing version (?:...) usable?
471 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
469 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
472 # Disable prompt stripping inside cell magics
470 # Disable prompt stripping inside cell magics
473 turnoff_re = re.compile(r'^%%')
471 turnoff_re = re.compile(r'^%%')
474 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
472 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
475
473
476
474
477 @CoroutineInputTransformer.wrap
475 @CoroutineInputTransformer.wrap
478 def leading_indent():
476 def leading_indent():
479 """Remove leading indentation.
477 """Remove leading indentation.
480
478
481 If the first line starts with a spaces or tabs, the same whitespace will be
479 If the first line starts with a spaces or tabs, the same whitespace will be
482 removed from each following line until it is reset.
480 removed from each following line until it is reset.
483 """
481 """
484 space_re = re.compile(r'^[ \t]+')
482 space_re = re.compile(r'^[ \t]+')
485 line = ''
483 line = ''
486 while True:
484 while True:
487 line = (yield line)
485 line = (yield line)
488
486
489 if line is None:
487 if line is None:
490 continue
488 continue
491
489
492 m = space_re.match(line)
490 m = space_re.match(line)
493 if m:
491 if m:
494 space = m.group(0)
492 space = m.group(0)
495 while line is not None:
493 while line is not None:
496 if line.startswith(space):
494 if line.startswith(space):
497 line = line[len(space):]
495 line = line[len(space):]
498 line = (yield line)
496 line = (yield line)
499 else:
497 else:
500 # No leading spaces - wait for reset
498 # No leading spaces - wait for reset
501 while line is not None:
499 while line is not None:
502 line = (yield line)
500 line = (yield line)
503
501
504
502
505 _assign_pat = \
503 _assign_pat = \
506 r'''(?P<lhs>(\s*)
504 r'''(?P<lhs>(\s*)
507 ([\w\.]+) # Initial identifier
505 ([\w\.]+) # Initial identifier
508 (\s*,\s*
506 (\s*,\s*
509 \*?[\w\.]+)* # Further identifiers for unpacking
507 \*?[\w\.]+)* # Further identifiers for unpacking
510 \s*?,? # Trailing comma
508 \s*?,? # Trailing comma
511 )
509 )
512 \s*=\s*
510 \s*=\s*
513 '''
511 '''
514
512
515 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
513 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
516 assign_system_template = '%s = get_ipython().getoutput(%r)'
514 assign_system_template = '%s = get_ipython().getoutput(%r)'
517 @StatelessInputTransformer.wrap
515 @StatelessInputTransformer.wrap
518 def assign_from_system(line):
516 def assign_from_system(line):
519 """Transform assignment from system commands (e.g. files = !ls)"""
517 """Transform assignment from system commands (e.g. files = !ls)"""
520 m = assign_system_re.match(line)
518 m = assign_system_re.match(line)
521 if m is None:
519 if m is None:
522 return line
520 return line
523
521
524 return assign_system_template % m.group('lhs', 'cmd')
522 return assign_system_template % m.group('lhs', 'cmd')
525
523
526 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
524 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
527 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
525 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
528 @StatelessInputTransformer.wrap
526 @StatelessInputTransformer.wrap
529 def assign_from_magic(line):
527 def assign_from_magic(line):
530 """Transform assignment from magic commands (e.g. a = %who_ls)"""
528 """Transform assignment from magic commands (e.g. a = %who_ls)"""
531 m = assign_magic_re.match(line)
529 m = assign_magic_re.match(line)
532 if m is None:
530 if m is None:
533 return line
531 return line
534 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
532 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
535 m_lhs, m_cmd = m.group('lhs', 'cmd')
533 m_lhs, m_cmd = m.group('lhs', 'cmd')
536 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
534 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
537 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
535 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
538 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
536 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
@@ -1,796 +1,788 b''
1 """Input transformer machinery to support IPython special syntax.
1 """Input transformer machinery to support IPython special syntax.
2
2
3 This includes the machinery to recognise and transform ``%magic`` commands,
3 This includes the machinery to recognise and transform ``%magic`` commands,
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5
5
6 Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
6 Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
7 deprecated in 7.0.
7 deprecated in 7.0.
8 """
8 """
9
9
10 # Copyright (c) IPython Development Team.
10 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12
12
13 import ast
13 import ast
14 import sys
14 import sys
15 from codeop import CommandCompiler, Compile
15 from codeop import CommandCompiler, Compile
16 import re
16 import re
17 import tokenize
17 import tokenize
18 from typing import List, Tuple, Optional, Any
18 from typing import List, Tuple, Optional, Any
19 import warnings
19 import warnings
20
20
21 _indent_re = re.compile(r'^[ \t]+')
21 _indent_re = re.compile(r'^[ \t]+')
22
22
23 def leading_empty_lines(lines):
23 def leading_empty_lines(lines):
24 """Remove leading empty lines
24 """Remove leading empty lines
25
25
26 If the leading lines are empty or contain only whitespace, they will be
26 If the leading lines are empty or contain only whitespace, they will be
27 removed.
27 removed.
28 """
28 """
29 if not lines:
29 if not lines:
30 return lines
30 return lines
31 for i, line in enumerate(lines):
31 for i, line in enumerate(lines):
32 if line and not line.isspace():
32 if line and not line.isspace():
33 return lines[i:]
33 return lines[i:]
34 return lines
34 return lines
35
35
36 def leading_indent(lines):
36 def leading_indent(lines):
37 """Remove leading indentation.
37 """Remove leading indentation.
38
38
39 If the first line starts with a spaces or tabs, the same whitespace will be
39 If the first line starts with a spaces or tabs, the same whitespace will be
40 removed from each following line in the cell.
40 removed from each following line in the cell.
41 """
41 """
42 if not lines:
42 if not lines:
43 return lines
43 return lines
44 m = _indent_re.match(lines[0])
44 m = _indent_re.match(lines[0])
45 if not m:
45 if not m:
46 return lines
46 return lines
47 space = m.group(0)
47 space = m.group(0)
48 n = len(space)
48 n = len(space)
49 return [l[n:] if l.startswith(space) else l
49 return [l[n:] if l.startswith(space) else l
50 for l in lines]
50 for l in lines]
51
51
52 class PromptStripper:
52 class PromptStripper:
53 """Remove matching input prompts from a block of input.
53 """Remove matching input prompts from a block of input.
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 prompt_re : regular expression
57 prompt_re : regular expression
58 A regular expression matching any input prompt (including continuation,
58 A regular expression matching any input prompt (including continuation,
59 e.g. ``...``)
59 e.g. ``...``)
60 initial_re : regular expression, optional
60 initial_re : regular expression, optional
61 A regular expression matching only the initial prompt, but not continuation.
61 A regular expression matching only the initial prompt, but not continuation.
62 If no initial expression is given, prompt_re will be used everywhere.
62 If no initial expression is given, prompt_re will be used everywhere.
63 Used mainly for plain Python prompts (``>>>``), where the continuation prompt
63 Used mainly for plain Python prompts (``>>>``), where the continuation prompt
64 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
64 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
65
65
66 Notes
66 Notes
67 -----
67 -----
68
68
69 If initial_re and prompt_re differ,
69 If initial_re and prompt_re differ,
70 only initial_re will be tested against the first line.
70 only initial_re will be tested against the first line.
71 If any prompt is found on the first two lines,
71 If any prompt is found on the first two lines,
72 prompts will be stripped from the rest of the block.
72 prompts will be stripped from the rest of the block.
73 """
73 """
74 def __init__(self, prompt_re, initial_re=None):
74 def __init__(self, prompt_re, initial_re=None):
75 self.prompt_re = prompt_re
75 self.prompt_re = prompt_re
76 self.initial_re = initial_re or prompt_re
76 self.initial_re = initial_re or prompt_re
77
77
78 def _strip(self, lines):
78 def _strip(self, lines):
79 return [self.prompt_re.sub('', l, count=1) for l in lines]
79 return [self.prompt_re.sub('', l, count=1) for l in lines]
80
80
81 def __call__(self, lines):
81 def __call__(self, lines):
82 if not lines:
82 if not lines:
83 return lines
83 return lines
84 if self.initial_re.match(lines[0]) or \
84 if self.initial_re.match(lines[0]) or \
85 (len(lines) > 1 and self.prompt_re.match(lines[1])):
85 (len(lines) > 1 and self.prompt_re.match(lines[1])):
86 return self._strip(lines)
86 return self._strip(lines)
87 return lines
87 return lines
88
88
89 classic_prompt = PromptStripper(
89 classic_prompt = PromptStripper(
90 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
90 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
91 initial_re=re.compile(r'^>>>( |$)')
91 initial_re=re.compile(r'^>>>( |$)')
92 )
92 )
93
93
94 ipython_prompt = PromptStripper(
94 ipython_prompt = PromptStripper(
95 re.compile(
95 re.compile(
96 r"""
96 r"""
97 ^( # Match from the beginning of a line, either:
97 ^( # Match from the beginning of a line, either:
98
98
99 # 1. First-line prompt:
99 # 1. First-line prompt:
100 ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
100 ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
101 In\ # The 'In' of the prompt, with a space
101 In\ # The 'In' of the prompt, with a space
102 \[\d+\]: # Command index, as displayed in the prompt
102 \[\d+\]: # Command index, as displayed in the prompt
103 \ # With a mandatory trailing space
103 \ # With a mandatory trailing space
104
104
105 | # ... or ...
105 | # ... or ...
106
106
107 # 2. The three dots of the multiline prompt
107 # 2. The three dots of the multiline prompt
108 \s* # All leading whitespace characters
108 \s* # All leading whitespace characters
109 \.{3,}: # The three (or more) dots
109 \.{3,}: # The three (or more) dots
110 \ ? # With an optional trailing space
110 \ ? # With an optional trailing space
111
111
112 )
112 )
113 """,
113 """,
114 re.VERBOSE,
114 re.VERBOSE,
115 )
115 )
116 )
116 )
117
117
118
118
119 def cell_magic(lines):
119 def cell_magic(lines):
120 if not lines or not lines[0].startswith('%%'):
120 if not lines or not lines[0].startswith('%%'):
121 return lines
121 return lines
122 if re.match(r'%%\w+\?', lines[0]):
122 if re.match(r'%%\w+\?', lines[0]):
123 # This case will be handled by help_end
123 # This case will be handled by help_end
124 return lines
124 return lines
125 magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
125 magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
126 body = ''.join(lines[1:])
126 body = ''.join(lines[1:])
127 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
127 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
128 % (magic_name, first_line, body)]
128 % (magic_name, first_line, body)]
129
129
130
130
131 def _find_assign_op(token_line) -> Optional[int]:
131 def _find_assign_op(token_line) -> Optional[int]:
132 """Get the index of the first assignment in the line ('=' not inside brackets)
132 """Get the index of the first assignment in the line ('=' not inside brackets)
133
133
134 Note: We don't try to support multiple special assignment (a = b = %foo)
134 Note: We don't try to support multiple special assignment (a = b = %foo)
135 """
135 """
136 paren_level = 0
136 paren_level = 0
137 for i, ti in enumerate(token_line):
137 for i, ti in enumerate(token_line):
138 s = ti.string
138 s = ti.string
139 if s == '=' and paren_level == 0:
139 if s == '=' and paren_level == 0:
140 return i
140 return i
141 if s in {'(','[','{'}:
141 if s in {'(','[','{'}:
142 paren_level += 1
142 paren_level += 1
143 elif s in {')', ']', '}'}:
143 elif s in {')', ']', '}'}:
144 if paren_level > 0:
144 if paren_level > 0:
145 paren_level -= 1
145 paren_level -= 1
146 return None
146 return None
147
147
148 def find_end_of_continued_line(lines, start_line: int):
148 def find_end_of_continued_line(lines, start_line: int):
149 """Find the last line of a line explicitly extended using backslashes.
149 """Find the last line of a line explicitly extended using backslashes.
150
150
151 Uses 0-indexed line numbers.
151 Uses 0-indexed line numbers.
152 """
152 """
153 end_line = start_line
153 end_line = start_line
154 while lines[end_line].endswith('\\\n'):
154 while lines[end_line].endswith('\\\n'):
155 end_line += 1
155 end_line += 1
156 if end_line >= len(lines):
156 if end_line >= len(lines):
157 break
157 break
158 return end_line
158 return end_line
159
159
160 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
160 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
161 r"""Assemble a single line from multiple continued line pieces
161 r"""Assemble a single line from multiple continued line pieces
162
162
163 Continued lines are lines ending in ``\``, and the line following the last
163 Continued lines are lines ending in ``\``, and the line following the last
164 ``\`` in the block.
164 ``\`` in the block.
165
165
166 For example, this code continues over multiple lines::
166 For example, this code continues over multiple lines::
167
167
168 if (assign_ix is not None) \
168 if (assign_ix is not None) \
169 and (len(line) >= assign_ix + 2) \
169 and (len(line) >= assign_ix + 2) \
170 and (line[assign_ix+1].string == '%') \
170 and (line[assign_ix+1].string == '%') \
171 and (line[assign_ix+2].type == tokenize.NAME):
171 and (line[assign_ix+2].type == tokenize.NAME):
172
172
173 This statement contains four continued line pieces.
173 This statement contains four continued line pieces.
174 Assembling these pieces into a single line would give::
174 Assembling these pieces into a single line would give::
175
175
176 if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
176 if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
177
177
178 This uses 0-indexed line numbers. *start* is (lineno, colno).
178 This uses 0-indexed line numbers. *start* is (lineno, colno).
179
179
180 Used to allow ``%magic`` and ``!system`` commands to be continued over
180 Used to allow ``%magic`` and ``!system`` commands to be continued over
181 multiple lines.
181 multiple lines.
182 """
182 """
183 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
183 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
184 return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
184 return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
185 + [parts[-1].rstrip()]) # Strip newline from last line
185 + [parts[-1].rstrip()]) # Strip newline from last line
186
186
187 class TokenTransformBase:
187 class TokenTransformBase:
188 """Base class for transformations which examine tokens.
188 """Base class for transformations which examine tokens.
189
189
190 Special syntax should not be transformed when it occurs inside strings or
190 Special syntax should not be transformed when it occurs inside strings or
191 comments. This is hard to reliably avoid with regexes. The solution is to
191 comments. This is hard to reliably avoid with regexes. The solution is to
192 tokenise the code as Python, and recognise the special syntax in the tokens.
192 tokenise the code as Python, and recognise the special syntax in the tokens.
193
193
194 IPython's special syntax is not valid Python syntax, so tokenising may go
194 IPython's special syntax is not valid Python syntax, so tokenising may go
195 wrong after the special syntax starts. These classes therefore find and
195 wrong after the special syntax starts. These classes therefore find and
196 transform *one* instance of special syntax at a time into regular Python
196 transform *one* instance of special syntax at a time into regular Python
197 syntax. After each transformation, tokens are regenerated to find the next
197 syntax. After each transformation, tokens are regenerated to find the next
198 piece of special syntax.
198 piece of special syntax.
199
199
200 Subclasses need to implement one class method (find)
200 Subclasses need to implement one class method (find)
201 and one regular method (transform).
201 and one regular method (transform).
202
202
203 The priority attribute can select which transformation to apply if multiple
203 The priority attribute can select which transformation to apply if multiple
204 transformers match in the same place. Lower numbers have higher priority.
204 transformers match in the same place. Lower numbers have higher priority.
205 This allows "%magic?" to be turned into a help call rather than a magic call.
205 This allows "%magic?" to be turned into a help call rather than a magic call.
206 """
206 """
207 # Lower numbers -> higher priority (for matches in the same location)
207 # Lower numbers -> higher priority (for matches in the same location)
208 priority = 10
208 priority = 10
209
209
210 def sortby(self):
210 def sortby(self):
211 return self.start_line, self.start_col, self.priority
211 return self.start_line, self.start_col, self.priority
212
212
213 def __init__(self, start):
213 def __init__(self, start):
214 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
214 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
215 self.start_col = start[1]
215 self.start_col = start[1]
216
216
217 @classmethod
217 @classmethod
218 def find(cls, tokens_by_line):
218 def find(cls, tokens_by_line):
219 """Find one instance of special syntax in the provided tokens.
219 """Find one instance of special syntax in the provided tokens.
220
220
221 Tokens are grouped into logical lines for convenience,
221 Tokens are grouped into logical lines for convenience,
222 so it is easy to e.g. look at the first token of each line.
222 so it is easy to e.g. look at the first token of each line.
223 *tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
223 *tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
224
224
225 This should return an instance of its class, pointing to the start
225 This should return an instance of its class, pointing to the start
226 position it has found, or None if it found no match.
226 position it has found, or None if it found no match.
227 """
227 """
228 raise NotImplementedError
228 raise NotImplementedError
229
229
230 def transform(self, lines: List[str]):
230 def transform(self, lines: List[str]):
231 """Transform one instance of special syntax found by ``find()``
231 """Transform one instance of special syntax found by ``find()``
232
232
233 Takes a list of strings representing physical lines,
233 Takes a list of strings representing physical lines,
234 returns a similar list of transformed lines.
234 returns a similar list of transformed lines.
235 """
235 """
236 raise NotImplementedError
236 raise NotImplementedError
237
237
238 class MagicAssign(TokenTransformBase):
238 class MagicAssign(TokenTransformBase):
239 """Transformer for assignments from magics (a = %foo)"""
239 """Transformer for assignments from magics (a = %foo)"""
240 @classmethod
240 @classmethod
241 def find(cls, tokens_by_line):
241 def find(cls, tokens_by_line):
242 """Find the first magic assignment (a = %foo) in the cell.
242 """Find the first magic assignment (a = %foo) in the cell.
243 """
243 """
244 for line in tokens_by_line:
244 for line in tokens_by_line:
245 assign_ix = _find_assign_op(line)
245 assign_ix = _find_assign_op(line)
246 if (assign_ix is not None) \
246 if (assign_ix is not None) \
247 and (len(line) >= assign_ix + 2) \
247 and (len(line) >= assign_ix + 2) \
248 and (line[assign_ix+1].string == '%') \
248 and (line[assign_ix+1].string == '%') \
249 and (line[assign_ix+2].type == tokenize.NAME):
249 and (line[assign_ix+2].type == tokenize.NAME):
250 return cls(line[assign_ix+1].start)
250 return cls(line[assign_ix+1].start)
251
251
252 def transform(self, lines: List[str]):
252 def transform(self, lines: List[str]):
253 """Transform a magic assignment found by the ``find()`` classmethod.
253 """Transform a magic assignment found by the ``find()`` classmethod.
254 """
254 """
255 start_line, start_col = self.start_line, self.start_col
255 start_line, start_col = self.start_line, self.start_col
256 lhs = lines[start_line][:start_col]
256 lhs = lines[start_line][:start_col]
257 end_line = find_end_of_continued_line(lines, start_line)
257 end_line = find_end_of_continued_line(lines, start_line)
258 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
258 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
259 assert rhs.startswith('%'), rhs
259 assert rhs.startswith('%'), rhs
260 magic_name, _, args = rhs[1:].partition(' ')
260 magic_name, _, args = rhs[1:].partition(' ')
261
261
262 lines_before = lines[:start_line]
262 lines_before = lines[:start_line]
263 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
263 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
264 new_line = lhs + call + '\n'
264 new_line = lhs + call + '\n'
265 lines_after = lines[end_line+1:]
265 lines_after = lines[end_line+1:]
266
266
267 return lines_before + [new_line] + lines_after
267 return lines_before + [new_line] + lines_after
268
268
269
269
270 class SystemAssign(TokenTransformBase):
270 class SystemAssign(TokenTransformBase):
271 """Transformer for assignments from system commands (a = !foo)"""
271 """Transformer for assignments from system commands (a = !foo)"""
272 @classmethod
272 @classmethod
273 def find(cls, tokens_by_line):
273 def find(cls, tokens_by_line):
274 """Find the first system assignment (a = !foo) in the cell.
274 """Find the first system assignment (a = !foo) in the cell.
275 """
275 """
276 for line in tokens_by_line:
276 for line in tokens_by_line:
277 assign_ix = _find_assign_op(line)
277 assign_ix = _find_assign_op(line)
278 if (assign_ix is not None) \
278 if (assign_ix is not None) \
279 and not line[assign_ix].line.strip().startswith('=') \
279 and not line[assign_ix].line.strip().startswith('=') \
280 and (len(line) >= assign_ix + 2) \
280 and (len(line) >= assign_ix + 2) \
281 and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
281 and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
282 ix = assign_ix + 1
282 ix = assign_ix + 1
283
283
284 while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
284 while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
285 if line[ix].string == '!':
285 if line[ix].string == '!':
286 return cls(line[ix].start)
286 return cls(line[ix].start)
287 elif not line[ix].string.isspace():
287 elif not line[ix].string.isspace():
288 break
288 break
289 ix += 1
289 ix += 1
290
290
291 def transform(self, lines: List[str]):
291 def transform(self, lines: List[str]):
292 """Transform a system assignment found by the ``find()`` classmethod.
292 """Transform a system assignment found by the ``find()`` classmethod.
293 """
293 """
294 start_line, start_col = self.start_line, self.start_col
294 start_line, start_col = self.start_line, self.start_col
295
295
296 lhs = lines[start_line][:start_col]
296 lhs = lines[start_line][:start_col]
297 end_line = find_end_of_continued_line(lines, start_line)
297 end_line = find_end_of_continued_line(lines, start_line)
298 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
298 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
299 assert rhs.startswith('!'), rhs
299 assert rhs.startswith('!'), rhs
300 cmd = rhs[1:]
300 cmd = rhs[1:]
301
301
302 lines_before = lines[:start_line]
302 lines_before = lines[:start_line]
303 call = "get_ipython().getoutput({!r})".format(cmd)
303 call = "get_ipython().getoutput({!r})".format(cmd)
304 new_line = lhs + call + '\n'
304 new_line = lhs + call + '\n'
305 lines_after = lines[end_line + 1:]
305 lines_after = lines[end_line + 1:]
306
306
307 return lines_before + [new_line] + lines_after
307 return lines_before + [new_line] + lines_after
308
308
309 # The escape sequences that define the syntax transformations IPython will
309 # The escape sequences that define the syntax transformations IPython will
310 # apply to user input. These can NOT be just changed here: many regular
310 # apply to user input. These can NOT be just changed here: many regular
311 # expressions and other parts of the code may use their hardcoded values, and
311 # expressions and other parts of the code may use their hardcoded values, and
312 # for all intents and purposes they constitute the 'IPython syntax', so they
312 # for all intents and purposes they constitute the 'IPython syntax', so they
313 # should be considered fixed.
313 # should be considered fixed.
314
314
315 ESC_SHELL = '!' # Send line to underlying system shell
315 ESC_SHELL = '!' # Send line to underlying system shell
316 ESC_SH_CAP = '!!' # Send line to system shell and capture output
316 ESC_SH_CAP = '!!' # Send line to system shell and capture output
317 ESC_HELP = '?' # Find information about object
317 ESC_HELP = '?' # Find information about object
318 ESC_HELP2 = '??' # Find extra-detailed information about object
318 ESC_HELP2 = '??' # Find extra-detailed information about object
319 ESC_MAGIC = '%' # Call magic function
319 ESC_MAGIC = '%' # Call magic function
320 ESC_MAGIC2 = '%%' # Call cell-magic function
320 ESC_MAGIC2 = '%%' # Call cell-magic function
321 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
321 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
322 ESC_QUOTE2 = ';' # Quote all args as a single string, call
322 ESC_QUOTE2 = ';' # Quote all args as a single string, call
323 ESC_PAREN = '/' # Call first argument with rest of line as arguments
323 ESC_PAREN = '/' # Call first argument with rest of line as arguments
324
324
325 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
325 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
326 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
326 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
327
327
328 def _make_help_call(target, esc, next_input=None):
328 def _make_help_call(target, esc):
329 """Prepares a pinfo(2)/psearch call from a target name and the escape
329 """Prepares a pinfo(2)/psearch call from a target name and the escape
330 (i.e. ? or ??)"""
330 (i.e. ? or ??)"""
331 method = 'pinfo2' if esc == '??' \
331 method = 'pinfo2' if esc == '??' \
332 else 'psearch' if '*' in target \
332 else 'psearch' if '*' in target \
333 else 'pinfo'
333 else 'pinfo'
334 arg = " ".join([method, target])
334 arg = " ".join([method, target])
335 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
335 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
336 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
336 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
337 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
337 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
338 if next_input is None:
338 return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
339 return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s)
339
340 else:
341 return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
342 (next_input, t_magic_name, t_magic_arg_s)
343
340
344 def _tr_help(content):
341 def _tr_help(content):
345 """Translate lines escaped with: ?
342 """Translate lines escaped with: ?
346
343
347 A naked help line should fire the intro help screen (shell.show_usage())
344 A naked help line should fire the intro help screen (shell.show_usage())
348 """
345 """
349 if not content:
346 if not content:
350 return 'get_ipython().show_usage()'
347 return 'get_ipython().show_usage()'
351
348
352 return _make_help_call(content, '?')
349 return _make_help_call(content, '?')
353
350
354 def _tr_help2(content):
351 def _tr_help2(content):
355 """Translate lines escaped with: ??
352 """Translate lines escaped with: ??
356
353
357 A naked help line should fire the intro help screen (shell.show_usage())
354 A naked help line should fire the intro help screen (shell.show_usage())
358 """
355 """
359 if not content:
356 if not content:
360 return 'get_ipython().show_usage()'
357 return 'get_ipython().show_usage()'
361
358
362 return _make_help_call(content, '??')
359 return _make_help_call(content, '??')
363
360
364 def _tr_magic(content):
361 def _tr_magic(content):
365 "Translate lines escaped with a percent sign: %"
362 "Translate lines escaped with a percent sign: %"
366 name, _, args = content.partition(' ')
363 name, _, args = content.partition(' ')
367 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
364 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
368
365
369 def _tr_quote(content):
366 def _tr_quote(content):
370 "Translate lines escaped with a comma: ,"
367 "Translate lines escaped with a comma: ,"
371 name, _, args = content.partition(' ')
368 name, _, args = content.partition(' ')
372 return '%s("%s")' % (name, '", "'.join(args.split()) )
369 return '%s("%s")' % (name, '", "'.join(args.split()) )
373
370
374 def _tr_quote2(content):
371 def _tr_quote2(content):
375 "Translate lines escaped with a semicolon: ;"
372 "Translate lines escaped with a semicolon: ;"
376 name, _, args = content.partition(' ')
373 name, _, args = content.partition(' ')
377 return '%s("%s")' % (name, args)
374 return '%s("%s")' % (name, args)
378
375
379 def _tr_paren(content):
376 def _tr_paren(content):
380 "Translate lines escaped with a slash: /"
377 "Translate lines escaped with a slash: /"
381 name, _, args = content.partition(' ')
378 name, _, args = content.partition(' ')
382 return '%s(%s)' % (name, ", ".join(args.split()))
379 return '%s(%s)' % (name, ", ".join(args.split()))
383
380
384 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
381 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
385 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
382 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
386 ESC_HELP : _tr_help,
383 ESC_HELP : _tr_help,
387 ESC_HELP2 : _tr_help2,
384 ESC_HELP2 : _tr_help2,
388 ESC_MAGIC : _tr_magic,
385 ESC_MAGIC : _tr_magic,
389 ESC_QUOTE : _tr_quote,
386 ESC_QUOTE : _tr_quote,
390 ESC_QUOTE2 : _tr_quote2,
387 ESC_QUOTE2 : _tr_quote2,
391 ESC_PAREN : _tr_paren }
388 ESC_PAREN : _tr_paren }
392
389
393 class EscapedCommand(TokenTransformBase):
390 class EscapedCommand(TokenTransformBase):
394 """Transformer for escaped commands like %foo, !foo, or /foo"""
391 """Transformer for escaped commands like %foo, !foo, or /foo"""
395 @classmethod
392 @classmethod
396 def find(cls, tokens_by_line):
393 def find(cls, tokens_by_line):
397 """Find the first escaped command (%foo, !foo, etc.) in the cell.
394 """Find the first escaped command (%foo, !foo, etc.) in the cell.
398 """
395 """
399 for line in tokens_by_line:
396 for line in tokens_by_line:
400 if not line:
397 if not line:
401 continue
398 continue
402 ix = 0
399 ix = 0
403 ll = len(line)
400 ll = len(line)
404 while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
401 while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
405 ix += 1
402 ix += 1
406 if ix >= ll:
403 if ix >= ll:
407 continue
404 continue
408 if line[ix].string in ESCAPE_SINGLES:
405 if line[ix].string in ESCAPE_SINGLES:
409 return cls(line[ix].start)
406 return cls(line[ix].start)
410
407
411 def transform(self, lines):
408 def transform(self, lines):
412 """Transform an escaped line found by the ``find()`` classmethod.
409 """Transform an escaped line found by the ``find()`` classmethod.
413 """
410 """
414 start_line, start_col = self.start_line, self.start_col
411 start_line, start_col = self.start_line, self.start_col
415
412
416 indent = lines[start_line][:start_col]
413 indent = lines[start_line][:start_col]
417 end_line = find_end_of_continued_line(lines, start_line)
414 end_line = find_end_of_continued_line(lines, start_line)
418 line = assemble_continued_line(lines, (start_line, start_col), end_line)
415 line = assemble_continued_line(lines, (start_line, start_col), end_line)
419
416
420 if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
417 if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
421 escape, content = line[:2], line[2:]
418 escape, content = line[:2], line[2:]
422 else:
419 else:
423 escape, content = line[:1], line[1:]
420 escape, content = line[:1], line[1:]
424
421
425 if escape in tr:
422 if escape in tr:
426 call = tr[escape](content)
423 call = tr[escape](content)
427 else:
424 else:
428 call = ''
425 call = ''
429
426
430 lines_before = lines[:start_line]
427 lines_before = lines[:start_line]
431 new_line = indent + call + '\n'
428 new_line = indent + call + '\n'
432 lines_after = lines[end_line + 1:]
429 lines_after = lines[end_line + 1:]
433
430
434 return lines_before + [new_line] + lines_after
431 return lines_before + [new_line] + lines_after
435
432
436 _help_end_re = re.compile(r"""(%{0,2}
433 _help_end_re = re.compile(r"""(%{0,2}
437 (?!\d)[\w*]+ # Variable name
434 (?!\d)[\w*]+ # Variable name
438 (\.(?!\d)[\w*]+)* # .etc.etc
435 (\.(?!\d)[\w*]+)* # .etc.etc
439 )
436 )
440 (\?\??)$ # ? or ??
437 (\?\??)$ # ? or ??
441 """,
438 """,
442 re.VERBOSE)
439 re.VERBOSE)
443
440
444 class HelpEnd(TokenTransformBase):
441 class HelpEnd(TokenTransformBase):
445 """Transformer for help syntax: obj? and obj??"""
442 """Transformer for help syntax: obj? and obj??"""
446 # This needs to be higher priority (lower number) than EscapedCommand so
443 # This needs to be higher priority (lower number) than EscapedCommand so
447 # that inspecting magics (%foo?) works.
444 # that inspecting magics (%foo?) works.
448 priority = 5
445 priority = 5
449
446
450 def __init__(self, start, q_locn):
447 def __init__(self, start, q_locn):
451 super().__init__(start)
448 super().__init__(start)
452 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
449 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
453 self.q_col = q_locn[1]
450 self.q_col = q_locn[1]
454
451
455 @classmethod
452 @classmethod
456 def find(cls, tokens_by_line):
453 def find(cls, tokens_by_line):
457 """Find the first help command (foo?) in the cell.
454 """Find the first help command (foo?) in the cell.
458 """
455 """
459 for line in tokens_by_line:
456 for line in tokens_by_line:
460 # Last token is NEWLINE; look at last but one
457 # Last token is NEWLINE; look at last but one
461 if len(line) > 2 and line[-2].string == '?':
458 if len(line) > 2 and line[-2].string == '?':
462 # Find the first token that's not INDENT/DEDENT
459 # Find the first token that's not INDENT/DEDENT
463 ix = 0
460 ix = 0
464 while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
461 while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
465 ix += 1
462 ix += 1
466 return cls(line[ix].start, line[-2].start)
463 return cls(line[ix].start, line[-2].start)
467
464
468 def transform(self, lines):
465 def transform(self, lines):
469 """Transform a help command found by the ``find()`` classmethod.
466 """Transform a help command found by the ``find()`` classmethod.
470 """
467 """
471 piece = ''.join(lines[self.start_line:self.q_line+1])
468 piece = ''.join(lines[self.start_line:self.q_line+1])
472 indent, content = piece[:self.start_col], piece[self.start_col:]
469 indent, content = piece[:self.start_col], piece[self.start_col:]
473 lines_before = lines[:self.start_line]
470 lines_before = lines[:self.start_line]
474 lines_after = lines[self.q_line + 1:]
471 lines_after = lines[self.q_line + 1:]
475
472
476 m = _help_end_re.search(content)
473 m = _help_end_re.search(content)
477 if not m:
474 if not m:
478 raise SyntaxError(content)
475 raise SyntaxError(content)
479 assert m is not None, content
476 assert m is not None, content
480 target = m.group(1)
477 target = m.group(1)
481 esc = m.group(3)
478 esc = m.group(3)
482
479
483 # If we're mid-command, put it back on the next prompt for the user.
484 next_input = None
485 if (not lines_before) and (not lines_after) \
486 and content.strip() != m.group(0):
487 next_input = content.rstrip('?\n')
488
480
489 call = _make_help_call(target, esc, next_input=next_input)
481 call = _make_help_call(target, esc)
490 new_line = indent + call + '\n'
482 new_line = indent + call + '\n'
491
483
492 return lines_before + [new_line] + lines_after
484 return lines_before + [new_line] + lines_after
493
485
494 def make_tokens_by_line(lines:List[str]):
486 def make_tokens_by_line(lines:List[str]):
495 """Tokenize a series of lines and group tokens by line.
487 """Tokenize a series of lines and group tokens by line.
496
488
497 The tokens for a multiline Python string or expression are grouped as one
489 The tokens for a multiline Python string or expression are grouped as one
498 line. All lines except the last lines should keep their line ending ('\\n',
490 line. All lines except the last lines should keep their line ending ('\\n',
499 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
491 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
500 for example when passing block of text to this function.
492 for example when passing block of text to this function.
501
493
502 """
494 """
503 # NL tokens are used inside multiline expressions, but also after blank
495 # NL tokens are used inside multiline expressions, but also after blank
504 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
496 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
505 # We want to group the former case together but split the latter, so we
497 # We want to group the former case together but split the latter, so we
506 # track parentheses level, similar to the internals of tokenize.
498 # track parentheses level, similar to the internals of tokenize.
507
499
508 # reexported from token on 3.7+
500 # reexported from token on 3.7+
509 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
501 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
510 tokens_by_line: List[List[Any]] = [[]]
502 tokens_by_line: List[List[Any]] = [[]]
511 if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
503 if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
512 warnings.warn(
504 warnings.warn(
513 "`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
505 "`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
514 stacklevel=2,
506 stacklevel=2,
515 )
507 )
516 parenlev = 0
508 parenlev = 0
517 try:
509 try:
518 for token in tokenize.generate_tokens(iter(lines).__next__):
510 for token in tokenize.generate_tokens(iter(lines).__next__):
519 tokens_by_line[-1].append(token)
511 tokens_by_line[-1].append(token)
520 if (token.type == NEWLINE) \
512 if (token.type == NEWLINE) \
521 or ((token.type == NL) and (parenlev <= 0)):
513 or ((token.type == NL) and (parenlev <= 0)):
522 tokens_by_line.append([])
514 tokens_by_line.append([])
523 elif token.string in {'(', '[', '{'}:
515 elif token.string in {'(', '[', '{'}:
524 parenlev += 1
516 parenlev += 1
525 elif token.string in {')', ']', '}'}:
517 elif token.string in {')', ']', '}'}:
526 if parenlev > 0:
518 if parenlev > 0:
527 parenlev -= 1
519 parenlev -= 1
528 except tokenize.TokenError:
520 except tokenize.TokenError:
529 # Input ended in a multiline string or expression. That's OK for us.
521 # Input ended in a multiline string or expression. That's OK for us.
530 pass
522 pass
531
523
532
524
533 if not tokens_by_line[-1]:
525 if not tokens_by_line[-1]:
534 tokens_by_line.pop()
526 tokens_by_line.pop()
535
527
536
528
537 return tokens_by_line
529 return tokens_by_line
538
530
539
531
540 def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
532 def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
541 """Check if the depth of brackets in the list of tokens drops below 0"""
533 """Check if the depth of brackets in the list of tokens drops below 0"""
542 parenlev = 0
534 parenlev = 0
543 for token in tokens:
535 for token in tokens:
544 if token.string in {"(", "[", "{"}:
536 if token.string in {"(", "[", "{"}:
545 parenlev += 1
537 parenlev += 1
546 elif token.string in {")", "]", "}"}:
538 elif token.string in {")", "]", "}"}:
547 parenlev -= 1
539 parenlev -= 1
548 if parenlev < 0:
540 if parenlev < 0:
549 return True
541 return True
550 return False
542 return False
551
543
552
544
553 def show_linewise_tokens(s: str):
545 def show_linewise_tokens(s: str):
554 """For investigation and debugging"""
546 """For investigation and debugging"""
555 if not s.endswith('\n'):
547 if not s.endswith('\n'):
556 s += '\n'
548 s += '\n'
557 lines = s.splitlines(keepends=True)
549 lines = s.splitlines(keepends=True)
558 for line in make_tokens_by_line(lines):
550 for line in make_tokens_by_line(lines):
559 print("Line -------")
551 print("Line -------")
560 for tokinfo in line:
552 for tokinfo in line:
561 print(" ", tokinfo)
553 print(" ", tokinfo)
562
554
563 # Arbitrary limit to prevent getting stuck in infinite loops
555 # Arbitrary limit to prevent getting stuck in infinite loops
564 TRANSFORM_LOOP_LIMIT = 500
556 TRANSFORM_LOOP_LIMIT = 500
565
557
566 class TransformerManager:
558 class TransformerManager:
567 """Applies various transformations to a cell or code block.
559 """Applies various transformations to a cell or code block.
568
560
569 The key methods for external use are ``transform_cell()``
561 The key methods for external use are ``transform_cell()``
570 and ``check_complete()``.
562 and ``check_complete()``.
571 """
563 """
572 def __init__(self):
564 def __init__(self):
573 self.cleanup_transforms = [
565 self.cleanup_transforms = [
574 leading_empty_lines,
566 leading_empty_lines,
575 leading_indent,
567 leading_indent,
576 classic_prompt,
568 classic_prompt,
577 ipython_prompt,
569 ipython_prompt,
578 ]
570 ]
579 self.line_transforms = [
571 self.line_transforms = [
580 cell_magic,
572 cell_magic,
581 ]
573 ]
582 self.token_transformers = [
574 self.token_transformers = [
583 MagicAssign,
575 MagicAssign,
584 SystemAssign,
576 SystemAssign,
585 EscapedCommand,
577 EscapedCommand,
586 HelpEnd,
578 HelpEnd,
587 ]
579 ]
588
580
589 def do_one_token_transform(self, lines):
581 def do_one_token_transform(self, lines):
590 """Find and run the transform earliest in the code.
582 """Find and run the transform earliest in the code.
591
583
592 Returns (changed, lines).
584 Returns (changed, lines).
593
585
594 This method is called repeatedly until changed is False, indicating
586 This method is called repeatedly until changed is False, indicating
595 that all available transformations are complete.
587 that all available transformations are complete.
596
588
597 The tokens following IPython special syntax might not be valid, so
589 The tokens following IPython special syntax might not be valid, so
598 the transformed code is retokenised every time to identify the next
590 the transformed code is retokenised every time to identify the next
599 piece of special syntax. Hopefully long code cells are mostly valid
591 piece of special syntax. Hopefully long code cells are mostly valid
600 Python, not using lots of IPython special syntax, so this shouldn't be
592 Python, not using lots of IPython special syntax, so this shouldn't be
601 a performance issue.
593 a performance issue.
602 """
594 """
603 tokens_by_line = make_tokens_by_line(lines)
595 tokens_by_line = make_tokens_by_line(lines)
604 candidates = []
596 candidates = []
605 for transformer_cls in self.token_transformers:
597 for transformer_cls in self.token_transformers:
606 transformer = transformer_cls.find(tokens_by_line)
598 transformer = transformer_cls.find(tokens_by_line)
607 if transformer:
599 if transformer:
608 candidates.append(transformer)
600 candidates.append(transformer)
609
601
610 if not candidates:
602 if not candidates:
611 # Nothing to transform
603 # Nothing to transform
612 return False, lines
604 return False, lines
613 ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
605 ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
614 for transformer in ordered_transformers:
606 for transformer in ordered_transformers:
615 try:
607 try:
616 return True, transformer.transform(lines)
608 return True, transformer.transform(lines)
617 except SyntaxError:
609 except SyntaxError:
618 pass
610 pass
619 return False, lines
611 return False, lines
620
612
621 def do_token_transforms(self, lines):
613 def do_token_transforms(self, lines):
622 for _ in range(TRANSFORM_LOOP_LIMIT):
614 for _ in range(TRANSFORM_LOOP_LIMIT):
623 changed, lines = self.do_one_token_transform(lines)
615 changed, lines = self.do_one_token_transform(lines)
624 if not changed:
616 if not changed:
625 return lines
617 return lines
626
618
627 raise RuntimeError("Input transformation still changing after "
619 raise RuntimeError("Input transformation still changing after "
628 "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
620 "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
629
621
630 def transform_cell(self, cell: str) -> str:
622 def transform_cell(self, cell: str) -> str:
631 """Transforms a cell of input code"""
623 """Transforms a cell of input code"""
632 if not cell.endswith('\n'):
624 if not cell.endswith('\n'):
633 cell += '\n' # Ensure the cell has a trailing newline
625 cell += '\n' # Ensure the cell has a trailing newline
634 lines = cell.splitlines(keepends=True)
626 lines = cell.splitlines(keepends=True)
635 for transform in self.cleanup_transforms + self.line_transforms:
627 for transform in self.cleanup_transforms + self.line_transforms:
636 lines = transform(lines)
628 lines = transform(lines)
637
629
638 lines = self.do_token_transforms(lines)
630 lines = self.do_token_transforms(lines)
639 return ''.join(lines)
631 return ''.join(lines)
640
632
641 def check_complete(self, cell: str):
633 def check_complete(self, cell: str):
642 """Return whether a block of code is ready to execute, or should be continued
634 """Return whether a block of code is ready to execute, or should be continued
643
635
644 Parameters
636 Parameters
645 ----------
637 ----------
646 cell : string
638 cell : string
647 Python input code, which can be multiline.
639 Python input code, which can be multiline.
648
640
649 Returns
641 Returns
650 -------
642 -------
651 status : str
643 status : str
652 One of 'complete', 'incomplete', or 'invalid' if source is not a
644 One of 'complete', 'incomplete', or 'invalid' if source is not a
653 prefix of valid code.
645 prefix of valid code.
654 indent_spaces : int or None
646 indent_spaces : int or None
655 The number of spaces by which to indent the next line of code. If
647 The number of spaces by which to indent the next line of code. If
656 status is not 'incomplete', this is None.
648 status is not 'incomplete', this is None.
657 """
649 """
658 # Remember if the lines ends in a new line.
650 # Remember if the lines ends in a new line.
659 ends_with_newline = False
651 ends_with_newline = False
660 for character in reversed(cell):
652 for character in reversed(cell):
661 if character == '\n':
653 if character == '\n':
662 ends_with_newline = True
654 ends_with_newline = True
663 break
655 break
664 elif character.strip():
656 elif character.strip():
665 break
657 break
666 else:
658 else:
667 continue
659 continue
668
660
669 if not ends_with_newline:
661 if not ends_with_newline:
670 # Append an newline for consistent tokenization
662 # Append an newline for consistent tokenization
671 # See https://bugs.python.org/issue33899
663 # See https://bugs.python.org/issue33899
672 cell += '\n'
664 cell += '\n'
673
665
674 lines = cell.splitlines(keepends=True)
666 lines = cell.splitlines(keepends=True)
675
667
676 if not lines:
668 if not lines:
677 return 'complete', None
669 return 'complete', None
678
670
679 if lines[-1].endswith('\\'):
671 if lines[-1].endswith('\\'):
680 # Explicit backslash continuation
672 # Explicit backslash continuation
681 return 'incomplete', find_last_indent(lines)
673 return 'incomplete', find_last_indent(lines)
682
674
683 try:
675 try:
684 for transform in self.cleanup_transforms:
676 for transform in self.cleanup_transforms:
685 if not getattr(transform, 'has_side_effects', False):
677 if not getattr(transform, 'has_side_effects', False):
686 lines = transform(lines)
678 lines = transform(lines)
687 except SyntaxError:
679 except SyntaxError:
688 return 'invalid', None
680 return 'invalid', None
689
681
690 if lines[0].startswith('%%'):
682 if lines[0].startswith('%%'):
691 # Special case for cell magics - completion marked by blank line
683 # Special case for cell magics - completion marked by blank line
692 if lines[-1].strip():
684 if lines[-1].strip():
693 return 'incomplete', find_last_indent(lines)
685 return 'incomplete', find_last_indent(lines)
694 else:
686 else:
695 return 'complete', None
687 return 'complete', None
696
688
697 try:
689 try:
698 for transform in self.line_transforms:
690 for transform in self.line_transforms:
699 if not getattr(transform, 'has_side_effects', False):
691 if not getattr(transform, 'has_side_effects', False):
700 lines = transform(lines)
692 lines = transform(lines)
701 lines = self.do_token_transforms(lines)
693 lines = self.do_token_transforms(lines)
702 except SyntaxError:
694 except SyntaxError:
703 return 'invalid', None
695 return 'invalid', None
704
696
705 tokens_by_line = make_tokens_by_line(lines)
697 tokens_by_line = make_tokens_by_line(lines)
706
698
707 # Bail if we got one line and there are more closing parentheses than
699 # Bail if we got one line and there are more closing parentheses than
708 # the opening ones
700 # the opening ones
709 if (
701 if (
710 len(lines) == 1
702 len(lines) == 1
711 and tokens_by_line
703 and tokens_by_line
712 and has_sunken_brackets(tokens_by_line[0])
704 and has_sunken_brackets(tokens_by_line[0])
713 ):
705 ):
714 return "invalid", None
706 return "invalid", None
715
707
716 if not tokens_by_line:
708 if not tokens_by_line:
717 return 'incomplete', find_last_indent(lines)
709 return 'incomplete', find_last_indent(lines)
718
710
719 if tokens_by_line[-1][-1].type != tokenize.ENDMARKER:
711 if tokens_by_line[-1][-1].type != tokenize.ENDMARKER:
720 # We're in a multiline string or expression
712 # We're in a multiline string or expression
721 return 'incomplete', find_last_indent(lines)
713 return 'incomplete', find_last_indent(lines)
722
714
723 newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
715 newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
724
716
725 # Pop the last line which only contains DEDENTs and ENDMARKER
717 # Pop the last line which only contains DEDENTs and ENDMARKER
726 last_token_line = None
718 last_token_line = None
727 if {t.type for t in tokens_by_line[-1]} in [
719 if {t.type for t in tokens_by_line[-1]} in [
728 {tokenize.DEDENT, tokenize.ENDMARKER},
720 {tokenize.DEDENT, tokenize.ENDMARKER},
729 {tokenize.ENDMARKER}
721 {tokenize.ENDMARKER}
730 ] and len(tokens_by_line) > 1:
722 ] and len(tokens_by_line) > 1:
731 last_token_line = tokens_by_line.pop()
723 last_token_line = tokens_by_line.pop()
732
724
733 while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
725 while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
734 tokens_by_line[-1].pop()
726 tokens_by_line[-1].pop()
735
727
736 if not tokens_by_line[-1]:
728 if not tokens_by_line[-1]:
737 return 'incomplete', find_last_indent(lines)
729 return 'incomplete', find_last_indent(lines)
738
730
739 if tokens_by_line[-1][-1].string == ':':
731 if tokens_by_line[-1][-1].string == ':':
740 # The last line starts a block (e.g. 'if foo:')
732 # The last line starts a block (e.g. 'if foo:')
741 ix = 0
733 ix = 0
742 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
734 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
743 ix += 1
735 ix += 1
744
736
745 indent = tokens_by_line[-1][ix].start[1]
737 indent = tokens_by_line[-1][ix].start[1]
746 return 'incomplete', indent + 4
738 return 'incomplete', indent + 4
747
739
748 if tokens_by_line[-1][0].line.endswith('\\'):
740 if tokens_by_line[-1][0].line.endswith('\\'):
749 return 'incomplete', None
741 return 'incomplete', None
750
742
751 # At this point, our checks think the code is complete (or invalid).
743 # At this point, our checks think the code is complete (or invalid).
752 # We'll use codeop.compile_command to check this with the real parser
744 # We'll use codeop.compile_command to check this with the real parser
753 try:
745 try:
754 with warnings.catch_warnings():
746 with warnings.catch_warnings():
755 warnings.simplefilter('error', SyntaxWarning)
747 warnings.simplefilter('error', SyntaxWarning)
756 res = compile_command(''.join(lines), symbol='exec')
748 res = compile_command(''.join(lines), symbol='exec')
757 except (SyntaxError, OverflowError, ValueError, TypeError,
749 except (SyntaxError, OverflowError, ValueError, TypeError,
758 MemoryError, SyntaxWarning):
750 MemoryError, SyntaxWarning):
759 return 'invalid', None
751 return 'invalid', None
760 else:
752 else:
761 if res is None:
753 if res is None:
762 return 'incomplete', find_last_indent(lines)
754 return 'incomplete', find_last_indent(lines)
763
755
764 if last_token_line and last_token_line[0].type == tokenize.DEDENT:
756 if last_token_line and last_token_line[0].type == tokenize.DEDENT:
765 if ends_with_newline:
757 if ends_with_newline:
766 return 'complete', None
758 return 'complete', None
767 return 'incomplete', find_last_indent(lines)
759 return 'incomplete', find_last_indent(lines)
768
760
769 # If there's a blank line at the end, assume we're ready to execute
761 # If there's a blank line at the end, assume we're ready to execute
770 if not lines[-1].strip():
762 if not lines[-1].strip():
771 return 'complete', None
763 return 'complete', None
772
764
773 return 'complete', None
765 return 'complete', None
774
766
775
767
776 def find_last_indent(lines):
768 def find_last_indent(lines):
777 m = _indent_re.match(lines[-1])
769 m = _indent_re.match(lines[-1])
778 if not m:
770 if not m:
779 return 0
771 return 0
780 return len(m.group(0).replace('\t', ' '*4))
772 return len(m.group(0).replace('\t', ' '*4))
781
773
782
774
783 class MaybeAsyncCompile(Compile):
775 class MaybeAsyncCompile(Compile):
784 def __init__(self, extra_flags=0):
776 def __init__(self, extra_flags=0):
785 super().__init__()
777 super().__init__()
786 self.flags |= extra_flags
778 self.flags |= extra_flags
787
779
788
780
789 class MaybeAsyncCommandCompiler(CommandCompiler):
781 class MaybeAsyncCommandCompiler(CommandCompiler):
790 def __init__(self, extra_flags=0):
782 def __init__(self, extra_flags=0):
791 self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
783 self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
792
784
793
785
794 _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
786 _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
795
787
796 compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
788 compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
@@ -1,484 +1,469 b''
1 import tokenize
1 import tokenize
2
2
3 from IPython.testing import tools as tt
3 from IPython.testing import tools as tt
4
4
5 from IPython.core import inputtransformer as ipt
5 from IPython.core import inputtransformer as ipt
6
6
7 def transform_and_reset(transformer):
7 def transform_and_reset(transformer):
8 transformer = transformer()
8 transformer = transformer()
9 def transform(inp):
9 def transform(inp):
10 try:
10 try:
11 return transformer.push(inp)
11 return transformer.push(inp)
12 finally:
12 finally:
13 transformer.reset()
13 transformer.reset()
14
14
15 return transform
15 return transform
16
16
17 # Transformer tests
17 # Transformer tests
18 def transform_checker(tests, transformer, **kwargs):
18 def transform_checker(tests, transformer, **kwargs):
19 """Utility to loop over test inputs"""
19 """Utility to loop over test inputs"""
20 transformer = transformer(**kwargs)
20 transformer = transformer(**kwargs)
21 try:
21 try:
22 for inp, tr in tests:
22 for inp, tr in tests:
23 if inp is None:
23 if inp is None:
24 out = transformer.reset()
24 out = transformer.reset()
25 else:
25 else:
26 out = transformer.push(inp)
26 out = transformer.push(inp)
27 assert out == tr
27 assert out == tr
28 finally:
28 finally:
29 transformer.reset()
29 transformer.reset()
30
30
31 # Data for all the syntax tests in the form of lists of pairs of
31 # Data for all the syntax tests in the form of lists of pairs of
32 # raw/transformed input. We store it here as a global dict so that we can use
32 # raw/transformed input. We store it here as a global dict so that we can use
33 # it both within single-function tests and also to validate the behavior of the
33 # it both within single-function tests and also to validate the behavior of the
34 # larger objects
34 # larger objects
35
35
36 syntax = \
36 syntax = \
37 dict(assign_system =
37 dict(assign_system =
38 [('a =! ls', "a = get_ipython().getoutput('ls')"),
38 [('a =! ls', "a = get_ipython().getoutput('ls')"),
39 ('b = !ls', "b = get_ipython().getoutput('ls')"),
39 ('b = !ls', "b = get_ipython().getoutput('ls')"),
40 ('c= !ls', "c = get_ipython().getoutput('ls')"),
40 ('c= !ls', "c = get_ipython().getoutput('ls')"),
41 ('d == !ls', 'd == !ls'), # Invalid syntax, but we leave == alone.
41 ('d == !ls', 'd == !ls'), # Invalid syntax, but we leave == alone.
42 ('x=1', 'x=1'), # normal input is unmodified
42 ('x=1', 'x=1'), # normal input is unmodified
43 (' ',' '), # blank lines are kept intact
43 (' ',' '), # blank lines are kept intact
44 # Tuple unpacking
44 # Tuple unpacking
45 ("a, b = !echo 'a\\nb'", "a, b = get_ipython().getoutput(\"echo 'a\\\\nb'\")"),
45 ("a, b = !echo 'a\\nb'", "a, b = get_ipython().getoutput(\"echo 'a\\\\nb'\")"),
46 ("a,= !echo 'a'", "a, = get_ipython().getoutput(\"echo 'a'\")"),
46 ("a,= !echo 'a'", "a, = get_ipython().getoutput(\"echo 'a'\")"),
47 ("a, *bc = !echo 'a\\nb\\nc'", "a, *bc = get_ipython().getoutput(\"echo 'a\\\\nb\\\\nc'\")"),
47 ("a, *bc = !echo 'a\\nb\\nc'", "a, *bc = get_ipython().getoutput(\"echo 'a\\\\nb\\\\nc'\")"),
48 # Tuple unpacking with regular Python expressions, not our syntax.
48 # Tuple unpacking with regular Python expressions, not our syntax.
49 ("a, b = range(2)", "a, b = range(2)"),
49 ("a, b = range(2)", "a, b = range(2)"),
50 ("a, = range(1)", "a, = range(1)"),
50 ("a, = range(1)", "a, = range(1)"),
51 ("a, *bc = range(3)", "a, *bc = range(3)"),
51 ("a, *bc = range(3)", "a, *bc = range(3)"),
52 ],
52 ],
53
53
54 assign_magic =
54 assign_magic =
55 [('a =% who', "a = get_ipython().run_line_magic('who', '')"),
55 [('a =% who', "a = get_ipython().run_line_magic('who', '')"),
56 ('b = %who', "b = get_ipython().run_line_magic('who', '')"),
56 ('b = %who', "b = get_ipython().run_line_magic('who', '')"),
57 ('c= %ls', "c = get_ipython().run_line_magic('ls', '')"),
57 ('c= %ls', "c = get_ipython().run_line_magic('ls', '')"),
58 ('d == %ls', 'd == %ls'), # Invalid syntax, but we leave == alone.
58 ('d == %ls', 'd == %ls'), # Invalid syntax, but we leave == alone.
59 ('x=1', 'x=1'), # normal input is unmodified
59 ('x=1', 'x=1'), # normal input is unmodified
60 (' ',' '), # blank lines are kept intact
60 (' ',' '), # blank lines are kept intact
61 ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"),
61 ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"),
62 ],
62 ],
63
63 classic_prompt=[
64 classic_prompt =
64 (">>> x=1", "x=1"),
65 [('>>> x=1', 'x=1'),
65 ("x=1", "x=1"), # normal input is unmodified
66 ('x=1', 'x=1'), # normal input is unmodified
66 (" ", " "), # blank lines are kept intact
67 (' ', ' '), # blank lines are kept intact
67 ],
68 ],
68 ipy_prompt=[
69
69 ("In [1]: x=1", "x=1"),
70 ipy_prompt =
70 ("x=1", "x=1"), # normal input is unmodified
71 [('In [1]: x=1', 'x=1'),
71 (" ", " "), # blank lines are kept intact
72 ('x=1', 'x=1'), # normal input is unmodified
72 ],
73 (' ',' '), # blank lines are kept intact
73 # Tests for the escape transformer to leave normal code alone
74 ],
74 escaped_noesc=[
75
75 (" ", " "),
76 # Tests for the escape transformer to leave normal code alone
76 ("x=1", "x=1"),
77 escaped_noesc =
77 ],
78 [ (' ', ' '),
78 # System calls
79 ('x=1', 'x=1'),
79 escaped_shell=[
80 ],
80 ("!ls", "get_ipython().system('ls')"),
81
81 # Double-escape shell, this means to capture the output of the
82 # System calls
82 # subprocess and return it
83 escaped_shell =
83 ("!!ls", "get_ipython().getoutput('ls')"),
84 [ ('!ls', "get_ipython().system('ls')"),
84 ],
85 # Double-escape shell, this means to capture the output of the
85 # Help/object info
86 # subprocess and return it
86 escaped_help=[
87 ('!!ls', "get_ipython().getoutput('ls')"),
87 ("?", "get_ipython().show_usage()"),
88 ],
88 ("?x1", "get_ipython().run_line_magic('pinfo', 'x1')"),
89
89 ("??x2", "get_ipython().run_line_magic('pinfo2', 'x2')"),
90 # Help/object info
90 ("?a.*s", "get_ipython().run_line_magic('psearch', 'a.*s')"),
91 escaped_help =
91 ("?%hist1", "get_ipython().run_line_magic('pinfo', '%hist1')"),
92 [ ('?', 'get_ipython().show_usage()'),
92 ("?%%hist2", "get_ipython().run_line_magic('pinfo', '%%hist2')"),
93 ('?x1', "get_ipython().run_line_magic('pinfo', 'x1')"),
93 ("?abc = qwe", "get_ipython().run_line_magic('pinfo', 'abc')"),
94 ('??x2', "get_ipython().run_line_magic('pinfo2', 'x2')"),
94 ],
95 ('?a.*s', "get_ipython().run_line_magic('psearch', 'a.*s')"),
95 end_help=[
96 ('?%hist1', "get_ipython().run_line_magic('pinfo', '%hist1')"),
96 ("x3?", "get_ipython().run_line_magic('pinfo', 'x3')"),
97 ('?%%hist2', "get_ipython().run_line_magic('pinfo', '%%hist2')"),
97 ("x4??", "get_ipython().run_line_magic('pinfo2', 'x4')"),
98 ('?abc = qwe', "get_ipython().run_line_magic('pinfo', 'abc')"),
98 ("%hist1?", "get_ipython().run_line_magic('pinfo', '%hist1')"),
99 ],
99 ("%hist2??", "get_ipython().run_line_magic('pinfo2', '%hist2')"),
100
100 ("%%hist3?", "get_ipython().run_line_magic('pinfo', '%%hist3')"),
101 end_help =
101 ("%%hist4??", "get_ipython().run_line_magic('pinfo2', '%%hist4')"),
102 [ ('x3?', "get_ipython().run_line_magic('pinfo', 'x3')"),
102 ("Ο€.foo?", "get_ipython().run_line_magic('pinfo', 'Ο€.foo')"),
103 ('x4??', "get_ipython().run_line_magic('pinfo2', 'x4')"),
103 ("f*?", "get_ipython().run_line_magic('psearch', 'f*')"),
104 ('%hist1?', "get_ipython().run_line_magic('pinfo', '%hist1')"),
104 ("ax.*aspe*?", "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"),
105 ('%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"),
105 ("a = abc?", "get_ipython().run_line_magic('pinfo', 'abc')"),
106 ('%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"),
106 ("a = abc.qe??", "get_ipython().run_line_magic('pinfo2', 'abc.qe')"),
107 ('%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"),
107 ("a = *.items?", "get_ipython().run_line_magic('psearch', '*.items')"),
108 ('Ο€.foo?', "get_ipython().run_line_magic('pinfo', 'Ο€.foo')"),
108 ("plot(a?", "get_ipython().run_line_magic('pinfo', 'a')"),
109 ('f*?', "get_ipython().run_line_magic('psearch', 'f*')"),
109 ("a*2 #comment?", "a*2 #comment?"),
110 ('ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"),
110 ],
111 ('a = abc?', "get_ipython().set_next_input('a = abc');"
111 # Explicit magic calls
112 "get_ipython().run_line_magic('pinfo', 'abc')"),
112 escaped_magic=[
113 ('a = abc.qe??', "get_ipython().set_next_input('a = abc.qe');"
113 ("%cd", "get_ipython().run_line_magic('cd', '')"),
114 "get_ipython().run_line_magic('pinfo2', 'abc.qe')"),
114 ("%cd /home", "get_ipython().run_line_magic('cd', '/home')"),
115 ('a = *.items?', "get_ipython().set_next_input('a = *.items');"
115 # Backslashes need to be escaped.
116 "get_ipython().run_line_magic('psearch', '*.items')"),
116 ("%cd C:\\User", "get_ipython().run_line_magic('cd', 'C:\\\\User')"),
117 ('plot(a?', "get_ipython().set_next_input('plot(a');"
117 (" %magic", " get_ipython().run_line_magic('magic', '')"),
118 "get_ipython().run_line_magic('pinfo', 'a')"),
118 ],
119 ('a*2 #comment?', 'a*2 #comment?'),
119 # Quoting with separate arguments
120 ],
120 escaped_quote=[
121
121 (",f", 'f("")'),
122 # Explicit magic calls
122 (",f x", 'f("x")'),
123 escaped_magic =
123 (" ,f y", ' f("y")'),
124 [ ('%cd', "get_ipython().run_line_magic('cd', '')"),
124 (",f a b", 'f("a", "b")'),
125 ('%cd /home', "get_ipython().run_line_magic('cd', '/home')"),
125 ],
126 # Backslashes need to be escaped.
126 # Quoting with single argument
127 ('%cd C:\\User', "get_ipython().run_line_magic('cd', 'C:\\\\User')"),
127 escaped_quote2=[
128 (' %magic', " get_ipython().run_line_magic('magic', '')"),
128 (";f", 'f("")'),
129 ],
129 (";f x", 'f("x")'),
130
130 (" ;f y", ' f("y")'),
131 # Quoting with separate arguments
131 (";f a b", 'f("a b")'),
132 escaped_quote =
132 ],
133 [ (',f', 'f("")'),
133 # Simply apply parens
134 (',f x', 'f("x")'),
134 escaped_paren=[
135 (' ,f y', ' f("y")'),
135 ("/f", "f()"),
136 (',f a b', 'f("a", "b")'),
136 ("/f x", "f(x)"),
137 ],
137 (" /f y", " f(y)"),
138
138 ("/f a b", "f(a, b)"),
139 # Quoting with single argument
139 ],
140 escaped_quote2 =
140 # Check that we transform prompts before other transforms
141 [ (';f', 'f("")'),
141 mixed=[
142 (';f x', 'f("x")'),
142 ("In [1]: %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"),
143 (' ;f y', ' f("y")'),
143 (">>> %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"),
144 (';f a b', 'f("a b")'),
144 ("In [2]: !ls", "get_ipython().system('ls')"),
145 ],
145 ("In [3]: abs?", "get_ipython().run_line_magic('pinfo', 'abs')"),
146
146 ("In [4]: b = %who", "b = get_ipython().run_line_magic('who', '')"),
147 # Simply apply parens
147 ],
148 escaped_paren =
148 )
149 [ ('/f', 'f()'),
150 ('/f x', 'f(x)'),
151 (' /f y', ' f(y)'),
152 ('/f a b', 'f(a, b)'),
153 ],
154
155 # Check that we transform prompts before other transforms
156 mixed =
157 [ ('In [1]: %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
158 ('>>> %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
159 ('In [2]: !ls', "get_ipython().system('ls')"),
160 ('In [3]: abs?', "get_ipython().run_line_magic('pinfo', 'abs')"),
161 ('In [4]: b = %who', "b = get_ipython().run_line_magic('who', '')"),
162 ],
163 )
164
149
165 # multiline syntax examples. Each of these should be a list of lists, with
150 # multiline syntax examples. Each of these should be a list of lists, with
166 # each entry itself having pairs of raw/transformed input. The union (with
151 # each entry itself having pairs of raw/transformed input. The union (with
167 # '\n'.join() of the transformed inputs is what the splitter should produce
152 # '\n'.join() of the transformed inputs is what the splitter should produce
168 # when fed the raw lines one at a time via push.
153 # when fed the raw lines one at a time via push.
169 syntax_ml = \
154 syntax_ml = \
170 dict(classic_prompt =
155 dict(classic_prompt =
171 [ [('>>> for i in range(10):','for i in range(10):'),
156 [ [('>>> for i in range(10):','for i in range(10):'),
172 ('... print i',' print i'),
157 ('... print i',' print i'),
173 ('... ', ''),
158 ('... ', ''),
174 ],
159 ],
175 [('>>> a="""','a="""'),
160 [('>>> a="""','a="""'),
176 ('... 123"""','123"""'),
161 ('... 123"""','123"""'),
177 ],
162 ],
178 [('a="""','a="""'),
163 [('a="""','a="""'),
179 ('... 123','123'),
164 ('... 123','123'),
180 ('... 456"""','456"""'),
165 ('... 456"""','456"""'),
181 ],
166 ],
182 [('a="""','a="""'),
167 [('a="""','a="""'),
183 ('>>> 123','123'),
168 ('>>> 123','123'),
184 ('... 456"""','456"""'),
169 ('... 456"""','456"""'),
185 ],
170 ],
186 [('a="""','a="""'),
171 [('a="""','a="""'),
187 ('123','123'),
172 ('123','123'),
188 ('... 456"""','... 456"""'),
173 ('... 456"""','... 456"""'),
189 ],
174 ],
190 [('....__class__','....__class__'),
175 [('....__class__','....__class__'),
191 ],
176 ],
192 [('a=5', 'a=5'),
177 [('a=5', 'a=5'),
193 ('...', ''),
178 ('...', ''),
194 ],
179 ],
195 [('>>> def f(x):', 'def f(x):'),
180 [('>>> def f(x):', 'def f(x):'),
196 ('...', ''),
181 ('...', ''),
197 ('... return x', ' return x'),
182 ('... return x', ' return x'),
198 ],
183 ],
199 [('board = """....', 'board = """....'),
184 [('board = """....', 'board = """....'),
200 ('....', '....'),
185 ('....', '....'),
201 ('...."""', '...."""'),
186 ('...."""', '...."""'),
202 ],
187 ],
203 ],
188 ],
204
189
205 ipy_prompt =
190 ipy_prompt =
206 [ [('In [24]: for i in range(10):','for i in range(10):'),
191 [ [('In [24]: for i in range(10):','for i in range(10):'),
207 (' ....: print i',' print i'),
192 (' ....: print i',' print i'),
208 (' ....: ', ''),
193 (' ....: ', ''),
209 ],
194 ],
210 [('In [24]: for i in range(10):','for i in range(10):'),
195 [('In [24]: for i in range(10):','for i in range(10):'),
211 # Qt console prompts expand with spaces, not dots
196 # Qt console prompts expand with spaces, not dots
212 (' ...: print i',' print i'),
197 (' ...: print i',' print i'),
213 (' ...: ', ''),
198 (' ...: ', ''),
214 ],
199 ],
215 [('In [24]: for i in range(10):','for i in range(10):'),
200 [('In [24]: for i in range(10):','for i in range(10):'),
216 # Sometimes whitespace preceding '...' has been removed
201 # Sometimes whitespace preceding '...' has been removed
217 ('...: print i',' print i'),
202 ('...: print i',' print i'),
218 ('...: ', ''),
203 ('...: ', ''),
219 ],
204 ],
220 [('In [24]: for i in range(10):','for i in range(10):'),
205 [('In [24]: for i in range(10):','for i in range(10):'),
221 # Space after last continuation prompt has been removed (issue #6674)
206 # Space after last continuation prompt has been removed (issue #6674)
222 ('...: print i',' print i'),
207 ('...: print i',' print i'),
223 ('...:', ''),
208 ('...:', ''),
224 ],
209 ],
225 [('In [2]: a="""','a="""'),
210 [('In [2]: a="""','a="""'),
226 (' ...: 123"""','123"""'),
211 (' ...: 123"""','123"""'),
227 ],
212 ],
228 [('a="""','a="""'),
213 [('a="""','a="""'),
229 (' ...: 123','123'),
214 (' ...: 123','123'),
230 (' ...: 456"""','456"""'),
215 (' ...: 456"""','456"""'),
231 ],
216 ],
232 [('a="""','a="""'),
217 [('a="""','a="""'),
233 ('In [1]: 123','123'),
218 ('In [1]: 123','123'),
234 (' ...: 456"""','456"""'),
219 (' ...: 456"""','456"""'),
235 ],
220 ],
236 [('a="""','a="""'),
221 [('a="""','a="""'),
237 ('123','123'),
222 ('123','123'),
238 (' ...: 456"""',' ...: 456"""'),
223 (' ...: 456"""',' ...: 456"""'),
239 ],
224 ],
240 ],
225 ],
241
226
242 multiline_datastructure_prompt =
227 multiline_datastructure_prompt =
243 [ [('>>> a = [1,','a = [1,'),
228 [ [('>>> a = [1,','a = [1,'),
244 ('... 2]','2]'),
229 ('... 2]','2]'),
245 ],
230 ],
246 ],
231 ],
247
232
248 multiline_datastructure =
233 multiline_datastructure =
249 [ [('b = ("%s"', None),
234 [ [('b = ("%s"', None),
250 ('# comment', None),
235 ('# comment', None),
251 ('%foo )', 'b = ("%s"\n# comment\n%foo )'),
236 ('%foo )', 'b = ("%s"\n# comment\n%foo )'),
252 ],
237 ],
253 ],
238 ],
254
239
255 multiline_string =
240 multiline_string =
256 [ [("'''foo?", None),
241 [ [("'''foo?", None),
257 ("bar'''", "'''foo?\nbar'''"),
242 ("bar'''", "'''foo?\nbar'''"),
258 ],
243 ],
259 ],
244 ],
260
245
261 leading_indent =
246 leading_indent =
262 [ [(' print "hi"','print "hi"'),
247 [ [(' print "hi"','print "hi"'),
263 ],
248 ],
264 [(' for a in range(5):','for a in range(5):'),
249 [(' for a in range(5):','for a in range(5):'),
265 (' a*2',' a*2'),
250 (' a*2',' a*2'),
266 ],
251 ],
267 [(' a="""','a="""'),
252 [(' a="""','a="""'),
268 (' 123"""','123"""'),
253 (' 123"""','123"""'),
269 ],
254 ],
270 [('a="""','a="""'),
255 [('a="""','a="""'),
271 (' 123"""',' 123"""'),
256 (' 123"""',' 123"""'),
272 ],
257 ],
273 ],
258 ],
274
259
275 cellmagic =
260 cellmagic =
276 [ [('%%foo a', None),
261 [ [('%%foo a', None),
277 (None, "get_ipython().run_cell_magic('foo', 'a', '')"),
262 (None, "get_ipython().run_cell_magic('foo', 'a', '')"),
278 ],
263 ],
279 [('%%bar 123', None),
264 [('%%bar 123', None),
280 ('hello', None),
265 ('hello', None),
281 (None , "get_ipython().run_cell_magic('bar', '123', 'hello')"),
266 (None , "get_ipython().run_cell_magic('bar', '123', 'hello')"),
282 ],
267 ],
283 [('a=5', 'a=5'),
268 [('a=5', 'a=5'),
284 ('%%cellmagic', '%%cellmagic'),
269 ('%%cellmagic', '%%cellmagic'),
285 ],
270 ],
286 ],
271 ],
287
272
288 escaped =
273 escaped =
289 [ [('%abc def \\', None),
274 [ [('%abc def \\', None),
290 ('ghi', "get_ipython().run_line_magic('abc', 'def ghi')"),
275 ('ghi', "get_ipython().run_line_magic('abc', 'def ghi')"),
291 ],
276 ],
292 [('%abc def \\', None),
277 [('%abc def \\', None),
293 ('ghi\\', None),
278 ('ghi\\', None),
294 (None, "get_ipython().run_line_magic('abc', 'def ghi')"),
279 (None, "get_ipython().run_line_magic('abc', 'def ghi')"),
295 ],
280 ],
296 ],
281 ],
297
282
298 assign_magic =
283 assign_magic =
299 [ [('a = %bc de \\', None),
284 [ [('a = %bc de \\', None),
300 ('fg', "a = get_ipython().run_line_magic('bc', 'de fg')"),
285 ('fg', "a = get_ipython().run_line_magic('bc', 'de fg')"),
301 ],
286 ],
302 [('a = %bc de \\', None),
287 [('a = %bc de \\', None),
303 ('fg\\', None),
288 ('fg\\', None),
304 (None, "a = get_ipython().run_line_magic('bc', 'de fg')"),
289 (None, "a = get_ipython().run_line_magic('bc', 'de fg')"),
305 ],
290 ],
306 ],
291 ],
307
292
308 assign_system =
293 assign_system =
309 [ [('a = !bc de \\', None),
294 [ [('a = !bc de \\', None),
310 ('fg', "a = get_ipython().getoutput('bc de fg')"),
295 ('fg', "a = get_ipython().getoutput('bc de fg')"),
311 ],
296 ],
312 [('a = !bc de \\', None),
297 [('a = !bc de \\', None),
313 ('fg\\', None),
298 ('fg\\', None),
314 (None, "a = get_ipython().getoutput('bc de fg')"),
299 (None, "a = get_ipython().getoutput('bc de fg')"),
315 ],
300 ],
316 ],
301 ],
317 )
302 )
318
303
319
304
320 def test_assign_system():
305 def test_assign_system():
321 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
306 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
322
307
323 def test_assign_magic():
308 def test_assign_magic():
324 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
309 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
325
310
326 def test_classic_prompt():
311 def test_classic_prompt():
327 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
312 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
328 for example in syntax_ml['classic_prompt']:
313 for example in syntax_ml['classic_prompt']:
329 transform_checker(example, ipt.classic_prompt)
314 transform_checker(example, ipt.classic_prompt)
330 for example in syntax_ml['multiline_datastructure_prompt']:
315 for example in syntax_ml['multiline_datastructure_prompt']:
331 transform_checker(example, ipt.classic_prompt)
316 transform_checker(example, ipt.classic_prompt)
332
317
333 # Check that we don't transform the second line if the first is obviously
318 # Check that we don't transform the second line if the first is obviously
334 # IPython syntax
319 # IPython syntax
335 transform_checker([
320 transform_checker([
336 ('%foo', '%foo'),
321 ('%foo', '%foo'),
337 ('>>> bar', '>>> bar'),
322 ('>>> bar', '>>> bar'),
338 ], ipt.classic_prompt)
323 ], ipt.classic_prompt)
339
324
340
325
341 def test_ipy_prompt():
326 def test_ipy_prompt():
342 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
327 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
343 for example in syntax_ml['ipy_prompt']:
328 for example in syntax_ml['ipy_prompt']:
344 transform_checker(example, ipt.ipy_prompt)
329 transform_checker(example, ipt.ipy_prompt)
345
330
346 # Check that we don't transform the second line if we're inside a cell magic
331 # Check that we don't transform the second line if we're inside a cell magic
347 transform_checker([
332 transform_checker([
348 ('%%foo', '%%foo'),
333 ('%%foo', '%%foo'),
349 ('In [1]: bar', 'In [1]: bar'),
334 ('In [1]: bar', 'In [1]: bar'),
350 ], ipt.ipy_prompt)
335 ], ipt.ipy_prompt)
351
336
352 def test_assemble_logical_lines():
337 def test_assemble_logical_lines():
353 tests = \
338 tests = \
354 [ [("a = \\", None),
339 [ [("a = \\", None),
355 ("123", "a = 123"),
340 ("123", "a = 123"),
356 ],
341 ],
357 [("a = \\", None), # Test resetting when within a multi-line string
342 [("a = \\", None), # Test resetting when within a multi-line string
358 ("12 *\\", None),
343 ("12 *\\", None),
359 (None, "a = 12 *"),
344 (None, "a = 12 *"),
360 ],
345 ],
361 [("# foo\\", "# foo\\"), # Comments can't be continued like this
346 [("# foo\\", "# foo\\"), # Comments can't be continued like this
362 ],
347 ],
363 ]
348 ]
364 for example in tests:
349 for example in tests:
365 transform_checker(example, ipt.assemble_logical_lines)
350 transform_checker(example, ipt.assemble_logical_lines)
366
351
367 def test_assemble_python_lines():
352 def test_assemble_python_lines():
368 tests = \
353 tests = \
369 [ [("a = '''", None),
354 [ [("a = '''", None),
370 ("abc'''", "a = '''\nabc'''"),
355 ("abc'''", "a = '''\nabc'''"),
371 ],
356 ],
372 [("a = '''", None), # Test resetting when within a multi-line string
357 [("a = '''", None), # Test resetting when within a multi-line string
373 ("def", None),
358 ("def", None),
374 (None, "a = '''\ndef"),
359 (None, "a = '''\ndef"),
375 ],
360 ],
376 [("a = [1,", None),
361 [("a = [1,", None),
377 ("2]", "a = [1,\n2]"),
362 ("2]", "a = [1,\n2]"),
378 ],
363 ],
379 [("a = [1,", None), # Test resetting when within a multi-line string
364 [("a = [1,", None), # Test resetting when within a multi-line string
380 ("2,", None),
365 ("2,", None),
381 (None, "a = [1,\n2,"),
366 (None, "a = [1,\n2,"),
382 ],
367 ],
383 [("a = '''", None), # Test line continuation within a multi-line string
368 [("a = '''", None), # Test line continuation within a multi-line string
384 ("abc\\", None),
369 ("abc\\", None),
385 ("def", None),
370 ("def", None),
386 ("'''", "a = '''\nabc\\\ndef\n'''"),
371 ("'''", "a = '''\nabc\\\ndef\n'''"),
387 ],
372 ],
388 ] + syntax_ml['multiline_datastructure']
373 ] + syntax_ml['multiline_datastructure']
389 for example in tests:
374 for example in tests:
390 transform_checker(example, ipt.assemble_python_lines)
375 transform_checker(example, ipt.assemble_python_lines)
391
376
392
377
393 def test_help_end():
378 def test_help_end():
394 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
379 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
395
380
396 def test_escaped_noesc():
381 def test_escaped_noesc():
397 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
382 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
398
383
399
384
400 def test_escaped_shell():
385 def test_escaped_shell():
401 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
386 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
402
387
403
388
404 def test_escaped_help():
389 def test_escaped_help():
405 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
390 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
406
391
407
392
408 def test_escaped_magic():
393 def test_escaped_magic():
409 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
394 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
410
395
411
396
412 def test_escaped_quote():
397 def test_escaped_quote():
413 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
398 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
414
399
415
400
416 def test_escaped_quote2():
401 def test_escaped_quote2():
417 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
402 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
418
403
419
404
420 def test_escaped_paren():
405 def test_escaped_paren():
421 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
406 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
422
407
423
408
424 def test_cellmagic():
409 def test_cellmagic():
425 for example in syntax_ml['cellmagic']:
410 for example in syntax_ml['cellmagic']:
426 transform_checker(example, ipt.cellmagic)
411 transform_checker(example, ipt.cellmagic)
427
412
428 line_example = [('%%bar 123', None),
413 line_example = [('%%bar 123', None),
429 ('hello', None),
414 ('hello', None),
430 ('' , "get_ipython().run_cell_magic('bar', '123', 'hello')"),
415 ('' , "get_ipython().run_cell_magic('bar', '123', 'hello')"),
431 ]
416 ]
432 transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True)
417 transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True)
433
418
434 def test_has_comment():
419 def test_has_comment():
435 tests = [('text', False),
420 tests = [('text', False),
436 ('text #comment', True),
421 ('text #comment', True),
437 ('text #comment\n', True),
422 ('text #comment\n', True),
438 ('#comment', True),
423 ('#comment', True),
439 ('#comment\n', True),
424 ('#comment\n', True),
440 ('a = "#string"', False),
425 ('a = "#string"', False),
441 ('a = "#string" # comment', True),
426 ('a = "#string" # comment', True),
442 ('a #comment not "string"', True),
427 ('a #comment not "string"', True),
443 ]
428 ]
444 tt.check_pairs(ipt.has_comment, tests)
429 tt.check_pairs(ipt.has_comment, tests)
445
430
446 @ipt.TokenInputTransformer.wrap
431 @ipt.TokenInputTransformer.wrap
447 def decistmt(tokens):
432 def decistmt(tokens):
448 """Substitute Decimals for floats in a string of statements.
433 """Substitute Decimals for floats in a string of statements.
449
434
450 Based on an example from the tokenize module docs.
435 Based on an example from the tokenize module docs.
451 """
436 """
452 result = []
437 result = []
453 for toknum, tokval, _, _, _ in tokens:
438 for toknum, tokval, _, _, _ in tokens:
454 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
439 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
455 yield from [
440 yield from [
456 (tokenize.NAME, 'Decimal'),
441 (tokenize.NAME, 'Decimal'),
457 (tokenize.OP, '('),
442 (tokenize.OP, '('),
458 (tokenize.STRING, repr(tokval)),
443 (tokenize.STRING, repr(tokval)),
459 (tokenize.OP, ')')
444 (tokenize.OP, ')')
460 ]
445 ]
461 else:
446 else:
462 yield (toknum, tokval)
447 yield (toknum, tokval)
463
448
464
449
465
450
466 def test_token_input_transformer():
451 def test_token_input_transformer():
467 tests = [('1.2', "Decimal ('1.2')"),
452 tests = [('1.2', "Decimal ('1.2')"),
468 ('"1.2"', '"1.2"'),
453 ('"1.2"', '"1.2"'),
469 ]
454 ]
470 tt.check_pairs(transform_and_reset(decistmt), tests)
455 tt.check_pairs(transform_and_reset(decistmt), tests)
471 ml_tests = \
456 ml_tests = \
472 [ [("a = 1.2; b = '''x", None),
457 [ [("a = 1.2; b = '''x", None),
473 ("y'''", "a =Decimal ('1.2');b ='''x\ny'''"),
458 ("y'''", "a =Decimal ('1.2');b ='''x\ny'''"),
474 ],
459 ],
475 [("a = [1.2,", None),
460 [("a = [1.2,", None),
476 ("3]", "a =[Decimal ('1.2'),\n3 ]"),
461 ("3]", "a =[Decimal ('1.2'),\n3 ]"),
477 ],
462 ],
478 [("a = '''foo", None), # Test resetting when within a multi-line string
463 [("a = '''foo", None), # Test resetting when within a multi-line string
479 ("bar", None),
464 ("bar", None),
480 (None, "a = '''foo\nbar"),
465 (None, "a = '''foo\nbar"),
481 ],
466 ],
482 ]
467 ]
483 for example in ml_tests:
468 for example in ml_tests:
484 transform_checker(example, decistmt)
469 transform_checker(example, decistmt)
@@ -1,405 +1,442 b''
1 """Tests for the token-based transformers in IPython.core.inputtransformer2
1 """Tests for the token-based transformers in IPython.core.inputtransformer2
2
2
3 Line-based transformers are the simpler ones; token-based transformers are
3 Line-based transformers are the simpler ones; token-based transformers are
4 more complex. See test_inputtransformer2_line for tests for line-based
4 more complex. See test_inputtransformer2_line for tests for line-based
5 transformations.
5 transformations.
6 """
6 """
7 import platform
7 import platform
8 import string
8 import string
9 import sys
9 import sys
10 from textwrap import dedent
10 from textwrap import dedent
11
11
12 import pytest
12 import pytest
13
13
14 from IPython.core import inputtransformer2 as ipt2
14 from IPython.core import inputtransformer2 as ipt2
15 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
15 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
16
16
17 MULTILINE_MAGIC = ("""\
17 MULTILINE_MAGIC = (
18 """\
18 a = f()
19 a = f()
19 %foo \\
20 %foo \\
20 bar
21 bar
21 g()
22 g()
22 """.splitlines(keepends=True), (2, 0), """\
23 """.splitlines(
24 keepends=True
25 ),
26 (2, 0),
27 """\
23 a = f()
28 a = f()
24 get_ipython().run_line_magic('foo', ' bar')
29 get_ipython().run_line_magic('foo', ' bar')
25 g()
30 g()
26 """.splitlines(keepends=True))
31 """.splitlines(
32 keepends=True
33 ),
34 )
27
35
28 INDENTED_MAGIC = ("""\
36 INDENTED_MAGIC = (
37 """\
29 for a in range(5):
38 for a in range(5):
30 %ls
39 %ls
31 """.splitlines(keepends=True), (2, 4), """\
40 """.splitlines(
41 keepends=True
42 ),
43 (2, 4),
44 """\
32 for a in range(5):
45 for a in range(5):
33 get_ipython().run_line_magic('ls', '')
46 get_ipython().run_line_magic('ls', '')
34 """.splitlines(keepends=True))
47 """.splitlines(
48 keepends=True
49 ),
50 )
35
51
36 CRLF_MAGIC = ([
52 CRLF_MAGIC = (
37 "a = f()\n",
53 ["a = f()\n", "%ls\r\n", "g()\n"],
38 "%ls\r\n",
54 (2, 0),
39 "g()\n"
55 ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"],
40 ], (2, 0), [
56 )
41 "a = f()\n",
57
42 "get_ipython().run_line_magic('ls', '')\n",
58 MULTILINE_MAGIC_ASSIGN = (
43 "g()\n"
59 """\
44 ])
45
46 MULTILINE_MAGIC_ASSIGN = ("""\
47 a = f()
60 a = f()
48 b = %foo \\
61 b = %foo \\
49 bar
62 bar
50 g()
63 g()
51 """.splitlines(keepends=True), (2, 4), """\
64 """.splitlines(
65 keepends=True
66 ),
67 (2, 4),
68 """\
52 a = f()
69 a = f()
53 b = get_ipython().run_line_magic('foo', ' bar')
70 b = get_ipython().run_line_magic('foo', ' bar')
54 g()
71 g()
55 """.splitlines(keepends=True))
72 """.splitlines(
73 keepends=True
74 ),
75 )
56
76
57 MULTILINE_SYSTEM_ASSIGN = ("""\
77 MULTILINE_SYSTEM_ASSIGN = ("""\
58 a = f()
78 a = f()
59 b = !foo \\
79 b = !foo \\
60 bar
80 bar
61 g()
81 g()
62 """.splitlines(keepends=True), (2, 4), """\
82 """.splitlines(keepends=True), (2, 4), """\
63 a = f()
83 a = f()
64 b = get_ipython().getoutput('foo bar')
84 b = get_ipython().getoutput('foo bar')
65 g()
85 g()
66 """.splitlines(keepends=True))
86 """.splitlines(keepends=True))
67
87
68 #####
88 #####
69
89
70 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
90 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
71 def test():
91 def test():
72 for i in range(1):
92 for i in range(1):
73 print(i)
93 print(i)
74 res =! ls
94 res =! ls
75 """.splitlines(keepends=True), (4, 7), '''\
95 """.splitlines(
96 keepends=True
97 ),
98 (4, 7),
99 """\
76 def test():
100 def test():
77 for i in range(1):
101 for i in range(1):
78 print(i)
102 print(i)
79 res =get_ipython().getoutput(\' ls\')
103 res =get_ipython().getoutput(\' ls\')
80 '''.splitlines(keepends=True))
104 """.splitlines(
105 keepends=True
106 ),
107 )
81
108
82 ######
109 ######
83
110
84 AUTOCALL_QUOTE = (
111 AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'])
85 [",f 1 2 3\n"], (1, 0),
86 ['f("1", "2", "3")\n']
87 )
88
112
89 AUTOCALL_QUOTE2 = (
113 AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n'])
90 [";f 1 2 3\n"], (1, 0),
91 ['f("1 2 3")\n']
92 )
93
114
94 AUTOCALL_PAREN = (
115 AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"])
95 ["/f 1 2 3\n"], (1, 0),
96 ['f(1, 2, 3)\n']
97 )
98
116
99 SIMPLE_HELP = (
117 SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"])
100 ["foo?\n"], (1, 0),
101 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
102 )
103
118
104 DETAILED_HELP = (
119 DETAILED_HELP = (
105 ["foo??\n"], (1, 0),
120 ["foo??\n"],
106 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
121 (1, 0),
122 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"],
107 )
123 )
108
124
109 MAGIC_HELP = (
125 MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"])
110 ["%foo?\n"], (1, 0),
111 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
112 )
113
126
114 HELP_IN_EXPR = (
127 HELP_IN_EXPR = (
115 ["a = b + c?\n"], (1, 0),
128 ["a = b + c?\n"],
116 ["get_ipython().set_next_input('a = b + c');"
129 (1, 0),
117 "get_ipython().run_line_magic('pinfo', 'c')\n"]
130 ["get_ipython().run_line_magic('pinfo', 'c')\n"],
118 )
131 )
119
132
120 HELP_CONTINUED_LINE = ("""\
133 HELP_CONTINUED_LINE = (
134 """\
121 a = \\
135 a = \\
122 zip?
136 zip?
123 """.splitlines(keepends=True), (1, 0),
137 """.splitlines(
124 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
138 keepends=True
139 ),
140 (1, 0),
141 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
125 )
142 )
126
143
127 HELP_MULTILINE = ("""\
144 HELP_MULTILINE = (
145 """\
128 (a,
146 (a,
129 b) = zip?
147 b) = zip?
130 """.splitlines(keepends=True), (1, 0),
148 """.splitlines(
131 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
149 keepends=True
150 ),
151 (1, 0),
152 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
132 )
153 )
133
154
134 HELP_UNICODE = (
155 HELP_UNICODE = (
135 ["Ο€.foo?\n"], (1, 0),
156 ["Ο€.foo?\n"],
136 ["get_ipython().run_line_magic('pinfo', 'Ο€.foo')\n"]
157 (1, 0),
158 ["get_ipython().run_line_magic('pinfo', 'Ο€.foo')\n"],
137 )
159 )
138
160
139
161
140 def null_cleanup_transformer(lines):
162 def null_cleanup_transformer(lines):
141 """
163 """
142 A cleanup transform that returns an empty list.
164 A cleanup transform that returns an empty list.
143 """
165 """
144 return []
166 return []
145
167
146
168
147 def test_check_make_token_by_line_never_ends_empty():
169 def test_check_make_token_by_line_never_ends_empty():
148 """
170 """
149 Check that not sequence of single or double characters ends up leading to en empty list of tokens
171 Check that not sequence of single or double characters ends up leading to en empty list of tokens
150 """
172 """
151 from string import printable
173 from string import printable
174
152 for c in printable:
175 for c in printable:
153 assert make_tokens_by_line(c)[-1] != []
176 assert make_tokens_by_line(c)[-1] != []
154 for k in printable:
177 for k in printable:
155 assert make_tokens_by_line(c + k)[-1] != []
178 assert make_tokens_by_line(c + k)[-1] != []
156
179
157
180
158 def check_find(transformer, case, match=True):
181 def check_find(transformer, case, match=True):
159 sample, expected_start, _ = case
182 sample, expected_start, _ = case
160 tbl = make_tokens_by_line(sample)
183 tbl = make_tokens_by_line(sample)
161 res = transformer.find(tbl)
184 res = transformer.find(tbl)
162 if match:
185 if match:
163 # start_line is stored 0-indexed, expected values are 1-indexed
186 # start_line is stored 0-indexed, expected values are 1-indexed
164 assert (res.start_line + 1, res.start_col) == expected_start
187 assert (res.start_line + 1, res.start_col) == expected_start
165 return res
188 return res
166 else:
189 else:
167 assert res is None
190 assert res is None
168
191
192
169 def check_transform(transformer_cls, case):
193 def check_transform(transformer_cls, case):
170 lines, start, expected = case
194 lines, start, expected = case
171 transformer = transformer_cls(start)
195 transformer = transformer_cls(start)
172 assert transformer.transform(lines) == expected
196 assert transformer.transform(lines) == expected
173
197
198
174 def test_continued_line():
199 def test_continued_line():
175 lines = MULTILINE_MAGIC_ASSIGN[0]
200 lines = MULTILINE_MAGIC_ASSIGN[0]
176 assert ipt2.find_end_of_continued_line(lines, 1) == 2
201 assert ipt2.find_end_of_continued_line(lines, 1) == 2
177
202
178 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
203 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
179
204
205
180 def test_find_assign_magic():
206 def test_find_assign_magic():
181 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
207 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
182 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
208 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
183 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
209 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
184
210
211
185 def test_transform_assign_magic():
212 def test_transform_assign_magic():
186 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
213 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
187
214
215
188 def test_find_assign_system():
216 def test_find_assign_system():
189 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
217 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
190 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
218 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
191 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
219 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
192 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
220 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
193 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
221 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
194
222
223
195 def test_transform_assign_system():
224 def test_transform_assign_system():
196 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
225 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
197 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
226 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
198
227
228
199 def test_find_magic_escape():
229 def test_find_magic_escape():
200 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
230 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
201 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
231 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
202 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
232 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
203
233
234
204 def test_transform_magic_escape():
235 def test_transform_magic_escape():
205 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
236 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
206 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
237 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
207 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
238 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
208
239
240
209 def test_find_autocalls():
241 def test_find_autocalls():
210 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
242 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
211 print("Testing %r" % case[0])
243 print("Testing %r" % case[0])
212 check_find(ipt2.EscapedCommand, case)
244 check_find(ipt2.EscapedCommand, case)
213
245
246
214 def test_transform_autocall():
247 def test_transform_autocall():
215 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
248 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
216 print("Testing %r" % case[0])
249 print("Testing %r" % case[0])
217 check_transform(ipt2.EscapedCommand, case)
250 check_transform(ipt2.EscapedCommand, case)
218
251
252
219 def test_find_help():
253 def test_find_help():
220 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
254 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
221 check_find(ipt2.HelpEnd, case)
255 check_find(ipt2.HelpEnd, case)
222
256
223 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
257 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
224 assert tf.q_line == 1
258 assert tf.q_line == 1
225 assert tf.q_col == 3
259 assert tf.q_col == 3
226
260
227 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
261 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
228 assert tf.q_line == 1
262 assert tf.q_line == 1
229 assert tf.q_col == 8
263 assert tf.q_col == 8
230
264
231 # ? in a comment does not trigger help
265 # ? in a comment does not trigger help
232 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
266 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
233 # Nor in a string
267 # Nor in a string
234 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
268 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
235
269
270
236 def test_transform_help():
271 def test_transform_help():
237 tf = ipt2.HelpEnd((1, 0), (1, 9))
272 tf = ipt2.HelpEnd((1, 0), (1, 9))
238 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
273 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
239
274
240 tf = ipt2.HelpEnd((1, 0), (2, 3))
275 tf = ipt2.HelpEnd((1, 0), (2, 3))
241 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
276 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
242
277
243 tf = ipt2.HelpEnd((1, 0), (2, 8))
278 tf = ipt2.HelpEnd((1, 0), (2, 8))
244 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
279 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
245
280
246 tf = ipt2.HelpEnd((1, 0), (1, 0))
281 tf = ipt2.HelpEnd((1, 0), (1, 0))
247 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
282 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
248
283
284
249 def test_find_assign_op_dedent():
285 def test_find_assign_op_dedent():
250 """
286 """
251 be careful that empty token like dedent are not counted as parens
287 be careful that empty token like dedent are not counted as parens
252 """
288 """
289
253 class Tk:
290 class Tk:
254 def __init__(self, s):
291 def __init__(self, s):
255 self.string = s
292 self.string = s
256
293
257 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
294 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
258 assert (
295 assert (
259 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
296 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
260 )
297 )
261
298
262
299
263 examples = [
300 examples = [
264 pytest.param("a = 1", "complete", None),
301 pytest.param("a = 1", "complete", None),
265 pytest.param("for a in range(5):", "incomplete", 4),
302 pytest.param("for a in range(5):", "incomplete", 4),
266 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
303 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
267 pytest.param("raise = 2", "invalid", None),
304 pytest.param("raise = 2", "invalid", None),
268 pytest.param("a = [1,\n2,", "incomplete", 0),
305 pytest.param("a = [1,\n2,", "incomplete", 0),
269 pytest.param("(\n))", "incomplete", 0),
306 pytest.param("(\n))", "incomplete", 0),
270 pytest.param("\\\r\n", "incomplete", 0),
307 pytest.param("\\\r\n", "incomplete", 0),
271 pytest.param("a = '''\n hi", "incomplete", 3),
308 pytest.param("a = '''\n hi", "incomplete", 3),
272 pytest.param("def a():\n x=1\n global x", "invalid", None),
309 pytest.param("def a():\n x=1\n global x", "invalid", None),
273 pytest.param(
310 pytest.param(
274 "a \\ ",
311 "a \\ ",
275 "invalid",
312 "invalid",
276 None,
313 None,
277 marks=pytest.mark.xfail(
314 marks=pytest.mark.xfail(
278 reason="Bug in python 3.9.8 – bpo 45738",
315 reason="Bug in python 3.9.8 – bpo 45738",
279 condition=sys.version_info
316 condition=sys.version_info
280 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
317 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
281 raises=SystemError,
318 raises=SystemError,
282 strict=True,
319 strict=True,
283 ),
320 ),
284 ), # Nothing allowed after backslash,
321 ), # Nothing allowed after backslash,
285 pytest.param("1\\\n+2", "complete", None),
322 pytest.param("1\\\n+2", "complete", None),
286 ]
323 ]
287
324
288
325
289 @pytest.mark.parametrize("code, expected, number", examples)
326 @pytest.mark.parametrize("code, expected, number", examples)
290 def test_check_complete_param(code, expected, number):
327 def test_check_complete_param(code, expected, number):
291 cc = ipt2.TransformerManager().check_complete
328 cc = ipt2.TransformerManager().check_complete
292 assert cc(code) == (expected, number)
329 assert cc(code) == (expected, number)
293
330
294
331
295 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
332 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
296 @pytest.mark.xfail(
333 @pytest.mark.xfail(
297 reason="Bug in python 3.9.8 – bpo 45738",
334 reason="Bug in python 3.9.8 – bpo 45738",
298 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
335 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
299 raises=SystemError,
336 raises=SystemError,
300 strict=True,
337 strict=True,
301 )
338 )
302 def test_check_complete():
339 def test_check_complete():
303 cc = ipt2.TransformerManager().check_complete
340 cc = ipt2.TransformerManager().check_complete
304
341
305 example = dedent("""
342 example = dedent(
343 """
306 if True:
344 if True:
307 a=1""" )
345 a=1"""
346 )
308
347
309 assert cc(example) == ("incomplete", 4)
348 assert cc(example) == ("incomplete", 4)
310 assert cc(example + "\n") == ("complete", None)
349 assert cc(example + "\n") == ("complete", None)
311 assert cc(example + "\n ") == ("complete", None)
350 assert cc(example + "\n ") == ("complete", None)
312
351
313 # no need to loop on all the letters/numbers.
352 # no need to loop on all the letters/numbers.
314 short = '12abAB'+string.printable[62:]
353 short = "12abAB" + string.printable[62:]
315 for c in short:
354 for c in short:
316 # test does not raise:
355 # test does not raise:
317 cc(c)
356 cc(c)
318 for k in short:
357 for k in short:
319 cc(c+k)
358 cc(c + k)
320
359
321 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
360 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
322
361
323
362
324 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
363 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
325 @pytest.mark.parametrize(
364 @pytest.mark.parametrize(
326 "value, expected",
365 "value, expected",
327 [
366 [
328 ('''def foo():\n """''', ("incomplete", 4)),
367 ('''def foo():\n """''', ("incomplete", 4)),
329 ("""async with example:\n pass""", ("incomplete", 4)),
368 ("""async with example:\n pass""", ("incomplete", 4)),
330 ("""async with example:\n pass\n """, ("complete", None)),
369 ("""async with example:\n pass\n """, ("complete", None)),
331 ],
370 ],
332 )
371 )
333 def test_check_complete_II(value, expected):
372 def test_check_complete_II(value, expected):
334 """
373 """
335 Test that multiple line strings are properly handled.
374 Test that multiple line strings are properly handled.
336
375
337 Separate test function for convenience
376 Separate test function for convenience
338
377
339 """
378 """
340 cc = ipt2.TransformerManager().check_complete
379 cc = ipt2.TransformerManager().check_complete
341 assert cc(value) == expected
380 assert cc(value) == expected
342
381
343
382
344 @pytest.mark.parametrize(
383 @pytest.mark.parametrize(
345 "value, expected",
384 "value, expected",
346 [
385 [
347 (")", ("invalid", None)),
386 (")", ("invalid", None)),
348 ("]", ("invalid", None)),
387 ("]", ("invalid", None)),
349 ("}", ("invalid", None)),
388 ("}", ("invalid", None)),
350 (")(", ("invalid", None)),
389 (")(", ("invalid", None)),
351 ("][", ("invalid", None)),
390 ("][", ("invalid", None)),
352 ("}{", ("invalid", None)),
391 ("}{", ("invalid", None)),
353 ("]()(", ("invalid", None)),
392 ("]()(", ("invalid", None)),
354 ("())(", ("invalid", None)),
393 ("())(", ("invalid", None)),
355 (")[](", ("invalid", None)),
394 (")[](", ("invalid", None)),
356 ("()](", ("invalid", None)),
395 ("()](", ("invalid", None)),
357 ],
396 ],
358 )
397 )
359 def test_check_complete_invalidates_sunken_brackets(value, expected):
398 def test_check_complete_invalidates_sunken_brackets(value, expected):
360 """
399 """
361 Test that a single line with more closing brackets than the opening ones is
400 Test that a single line with more closing brackets than the opening ones is
362 interpreted as invalid
401 interpreted as invalid
363 """
402 """
364 cc = ipt2.TransformerManager().check_complete
403 cc = ipt2.TransformerManager().check_complete
365 assert cc(value) == expected
404 assert cc(value) == expected
366
405
367
406
368 def test_null_cleanup_transformer():
407 def test_null_cleanup_transformer():
369 manager = ipt2.TransformerManager()
408 manager = ipt2.TransformerManager()
370 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
409 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
371 assert manager.transform_cell("") == ""
410 assert manager.transform_cell("") == ""
372
411
373
412
374
375
376 def test_side_effects_I():
413 def test_side_effects_I():
377 count = 0
414 count = 0
415
378 def counter(lines):
416 def counter(lines):
379 nonlocal count
417 nonlocal count
380 count += 1
418 count += 1
381 return lines
419 return lines
382
420
383 counter.has_side_effects = True
421 counter.has_side_effects = True
384
422
385 manager = ipt2.TransformerManager()
423 manager = ipt2.TransformerManager()
386 manager.cleanup_transforms.insert(0, counter)
424 manager.cleanup_transforms.insert(0, counter)
387 assert manager.check_complete("a=1\n") == ('complete', None)
425 assert manager.check_complete("a=1\n") == ("complete", None)
388 assert count == 0
426 assert count == 0
389
427
390
428
391
392
393 def test_side_effects_II():
429 def test_side_effects_II():
394 count = 0
430 count = 0
431
395 def counter(lines):
432 def counter(lines):
396 nonlocal count
433 nonlocal count
397 count += 1
434 count += 1
398 return lines
435 return lines
399
436
400 counter.has_side_effects = True
437 counter.has_side_effects = True
401
438
402 manager = ipt2.TransformerManager()
439 manager = ipt2.TransformerManager()
403 manager.line_transforms.insert(0, counter)
440 manager.line_transforms.insert(0, counter)
404 assert manager.check_complete("b=1\n") == ('complete', None)
441 assert manager.check_complete("b=1\n") == ("complete", None)
405 assert count == 0
442 assert count == 0
@@ -1,1059 +1,1064 b''
1 ============
1 ============
2 8.x Series
2 8.x Series
3 ============
3 ============
4
4
5
5
6 .. _version 8.3.0:
6 .. _version 8.3.0:
7
7
8 IPython 8.3.0
8 IPython 8.3.0
9 -------------
9 -------------
10
10
11 - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call
12 ``set_next_input`` as most frontend allow proper multiline editing and it was
13 causing issues for many users of multi-cell frontends.
14
15
11 - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
16 - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
12 the info object when frontend provide it.
17 the info object when frontend provide it.
13
18
14 .. _version 8.2.0:
19 .. _version 8.2.0:
15
20
16 IPython 8.2.0
21 IPython 8.2.0
17 -------------
22 -------------
18
23
19 IPython 8.2 mostly bring bugfixes to IPython.
24 IPython 8.2 mostly bring bugfixes to IPython.
20
25
21 - Auto-suggestion can now be elected with the ``end`` key. :ghpull:`13566`
26 - Auto-suggestion can now be elected with the ``end`` key. :ghpull:`13566`
22 - Some traceback issues with ``assert etb is not None`` have been fixed. :ghpull:`13588`
27 - Some traceback issues with ``assert etb is not None`` have been fixed. :ghpull:`13588`
23 - History is now pulled from the sqitel database and not from in-memory.
28 - History is now pulled from the sqitel database and not from in-memory.
24 In particular when using the ``%paste`` magic, the content of the pasted text will
29 In particular when using the ``%paste`` magic, the content of the pasted text will
25 be part of the history and not the verbatim text ``%paste`` anymore. :ghpull:`13592`
30 be part of the history and not the verbatim text ``%paste`` anymore. :ghpull:`13592`
26 - Fix ``Ctrl-\\`` exit cleanup :ghpull:`13603`
31 - Fix ``Ctrl-\\`` exit cleanup :ghpull:`13603`
27 - Fixes to ``ultratb`` ipdb support when used outside of IPython. :ghpull:`13498`
32 - Fixes to ``ultratb`` ipdb support when used outside of IPython. :ghpull:`13498`
28
33
29
34
30 I am still trying to fix and investigate :ghissue:`13598`, which seem to be
35 I am still trying to fix and investigate :ghissue:`13598`, which seem to be
31 random, and would appreciate help if you find reproducible minimal case. I've
36 random, and would appreciate help if you find reproducible minimal case. I've
32 tried to make various changes to the codebase to mitigate it, but a proper fix
37 tried to make various changes to the codebase to mitigate it, but a proper fix
33 will be difficult without understanding the cause.
38 will be difficult without understanding the cause.
34
39
35
40
36 All the issues on pull-requests for this release can be found in the `8.2
41 All the issues on pull-requests for this release can be found in the `8.2
37 milestone. <https://github.com/ipython/ipython/milestone/100>`__ . And some
42 milestone. <https://github.com/ipython/ipython/milestone/100>`__ . And some
38 documentation only PR can be found as part of the `7.33 milestone
43 documentation only PR can be found as part of the `7.33 milestone
39 <https://github.com/ipython/ipython/milestone/101>`__ (currently not released).
44 <https://github.com/ipython/ipython/milestone/101>`__ (currently not released).
40
45
41 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
46 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
42 work on IPython and related libraries.
47 work on IPython and related libraries.
43
48
44 .. _version 8.1.1:
49 .. _version 8.1.1:
45
50
46 IPython 8.1.1
51 IPython 8.1.1
47 -------------
52 -------------
48
53
49 Fix an issue with virtualenv and Python 3.8 introduced in 8.1
54 Fix an issue with virtualenv and Python 3.8 introduced in 8.1
50
55
51 Revert :ghpull:`13537` (fix an issue with symlinks in virtualenv) that raises an
56 Revert :ghpull:`13537` (fix an issue with symlinks in virtualenv) that raises an
52 error in Python 3.8, and fixed in a different way in :ghpull:`13559`.
57 error in Python 3.8, and fixed in a different way in :ghpull:`13559`.
53
58
54 .. _version 8.1:
59 .. _version 8.1:
55
60
56 IPython 8.1.0
61 IPython 8.1.0
57 -------------
62 -------------
58
63
59 IPython 8.1 is the first minor release after 8.0 and fixes a number of bugs and
64 IPython 8.1 is the first minor release after 8.0 and fixes a number of bugs and
60 Update a few behavior that were problematic with the 8.0 as with many new major
65 Update a few behavior that were problematic with the 8.0 as with many new major
61 release.
66 release.
62
67
63 Note that beyond the changes listed here, IPython 8.1.0 also contains all the
68 Note that beyond the changes listed here, IPython 8.1.0 also contains all the
64 features listed in :ref:`version 7.32`.
69 features listed in :ref:`version 7.32`.
65
70
66 - Misc and multiple fixes around quotation auto-closing. It is now disabled by
71 - Misc and multiple fixes around quotation auto-closing. It is now disabled by
67 default. Run with ``TerminalInteractiveShell.auto_match=True`` to re-enabled
72 default. Run with ``TerminalInteractiveShell.auto_match=True`` to re-enabled
68 - Require pygments>=2.4.0 :ghpull:`13459`, this was implicit in the code, but
73 - Require pygments>=2.4.0 :ghpull:`13459`, this was implicit in the code, but
69 is now explicit in ``setup.cfg``/``setup.py``
74 is now explicit in ``setup.cfg``/``setup.py``
70 - Docs improvement of ``core.magic_arguments`` examples. :ghpull:`13433`
75 - Docs improvement of ``core.magic_arguments`` examples. :ghpull:`13433`
71 - Multi-line edit executes too early with await. :ghpull:`13424`
76 - Multi-line edit executes too early with await. :ghpull:`13424`
72
77
73 - ``black`` is back as an optional dependency, and autoformatting disabled by
78 - ``black`` is back as an optional dependency, and autoformatting disabled by
74 default until some fixes are implemented (black improperly reformat magics).
79 default until some fixes are implemented (black improperly reformat magics).
75 :ghpull:`13471` Additionally the ability to use ``yapf`` as a code
80 :ghpull:`13471` Additionally the ability to use ``yapf`` as a code
76 reformatter has been added :ghpull:`13528` . You can use
81 reformatter has been added :ghpull:`13528` . You can use
77 ``TerminalInteractiveShell.autoformatter="black"``,
82 ``TerminalInteractiveShell.autoformatter="black"``,
78 ``TerminalInteractiveShell.autoformatter="yapf"`` to re-enable auto formating
83 ``TerminalInteractiveShell.autoformatter="yapf"`` to re-enable auto formating
79 with black, or switch to yapf.
84 with black, or switch to yapf.
80
85
81 - Fix and issue where ``display`` was not defined.
86 - Fix and issue where ``display`` was not defined.
82
87
83 - Auto suggestions are now configurable. Currently only
88 - Auto suggestions are now configurable. Currently only
84 ``AutoSuggestFromHistory`` (default) and ``None``. new provider contribution
89 ``AutoSuggestFromHistory`` (default) and ``None``. new provider contribution
85 welcomed. :ghpull:`13475`
90 welcomed. :ghpull:`13475`
86
91
87 - multiple packaging/testing improvement to simplify downstream packaging
92 - multiple packaging/testing improvement to simplify downstream packaging
88 (xfail with reasons, try to not access network...).
93 (xfail with reasons, try to not access network...).
89
94
90 - Update deprecation. ``InteractiveShell.magic`` internal method has been
95 - Update deprecation. ``InteractiveShell.magic`` internal method has been
91 deprecated for many years but did not emit a warning until now.
96 deprecated for many years but did not emit a warning until now.
92
97
93 - internal ``appended_to_syspath`` context manager has been deprecated.
98 - internal ``appended_to_syspath`` context manager has been deprecated.
94
99
95 - fix an issue with symlinks in virtualenv :ghpull:`13537` (Reverted in 8.1.1)
100 - fix an issue with symlinks in virtualenv :ghpull:`13537` (Reverted in 8.1.1)
96
101
97 - Fix an issue with vim mode, where cursor would not be reset on exit :ghpull:`13472`
102 - Fix an issue with vim mode, where cursor would not be reset on exit :ghpull:`13472`
98
103
99 - ipython directive now remove only known pseudo-decorators :ghpull:`13532`
104 - ipython directive now remove only known pseudo-decorators :ghpull:`13532`
100
105
101 - ``IPython/lib/security`` which used to be used for jupyter notebook has been
106 - ``IPython/lib/security`` which used to be used for jupyter notebook has been
102 removed.
107 removed.
103
108
104 - Fix an issue where ``async with`` would execute on new lines. :ghpull:`13436`
109 - Fix an issue where ``async with`` would execute on new lines. :ghpull:`13436`
105
110
106
111
107 We want to remind users that IPython is part of the Jupyter organisations, and
112 We want to remind users that IPython is part of the Jupyter organisations, and
108 thus governed by a Code of Conduct. Some of the behavior we have seen on GitHub is not acceptable.
113 thus governed by a Code of Conduct. Some of the behavior we have seen on GitHub is not acceptable.
109 Abuse and non-respectful comments on discussion will not be tolerated.
114 Abuse and non-respectful comments on discussion will not be tolerated.
110
115
111 Many thanks to all the contributors to this release, many of the above fixed issue and
116 Many thanks to all the contributors to this release, many of the above fixed issue and
112 new features where done by first time contributors, showing there is still
117 new features where done by first time contributors, showing there is still
113 plenty of easy contribution possible in IPython
118 plenty of easy contribution possible in IPython
114 . You can find all individual contributions
119 . You can find all individual contributions
115 to this milestone `on github <https://github.com/ipython/ipython/milestone/91>`__.
120 to this milestone `on github <https://github.com/ipython/ipython/milestone/91>`__.
116
121
117 Thanks as well to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
122 Thanks as well to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
118 work on IPython and related libraries. In particular the Lazy autoloading of
123 work on IPython and related libraries. In particular the Lazy autoloading of
119 magics that you will find described in the 7.32 release notes.
124 magics that you will find described in the 7.32 release notes.
120
125
121
126
122 .. _version 8.0.1:
127 .. _version 8.0.1:
123
128
124 IPython 8.0.1 (CVE-2022-21699)
129 IPython 8.0.1 (CVE-2022-21699)
125 ------------------------------
130 ------------------------------
126
131
127 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
132 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
128 values in order to prevent potential Execution with Unnecessary Privileges.
133 values in order to prevent potential Execution with Unnecessary Privileges.
129
134
130 Almost all version of IPython looks for configuration and profiles in current
135 Almost all version of IPython looks for configuration and profiles in current
131 working directory. Since IPython was developed before pip and environments
136 working directory. Since IPython was developed before pip and environments
132 existed it was used a convenient way to load code/packages in a project
137 existed it was used a convenient way to load code/packages in a project
133 dependant way.
138 dependant way.
134
139
135 In 2022, it is not necessary anymore, and can lead to confusing behavior where
140 In 2022, it is not necessary anymore, and can lead to confusing behavior where
136 for example cloning a repository and starting IPython or loading a notebook from
141 for example cloning a repository and starting IPython or loading a notebook from
137 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
142 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
138 code execution.
143 code execution.
139
144
140
145
141 I did not find any standard way for packaged to advertise CVEs they fix, I'm
146 I did not find any standard way for packaged to advertise CVEs they fix, I'm
142 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
147 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
143 list the CVEs that should have been fixed. This attribute is informational only
148 list the CVEs that should have been fixed. This attribute is informational only
144 as if a executable has a flaw, this value can always be changed by an attacker.
149 as if a executable has a flaw, this value can always be changed by an attacker.
145
150
146 .. code::
151 .. code::
147
152
148 In [1]: import IPython
153 In [1]: import IPython
149
154
150 In [2]: IPython.__patched_cves__
155 In [2]: IPython.__patched_cves__
151 Out[2]: {'CVE-2022-21699'}
156 Out[2]: {'CVE-2022-21699'}
152
157
153 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
158 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
154 Out[3]: True
159 Out[3]: True
155
160
156 Thus starting with this version:
161 Thus starting with this version:
157
162
158 - The current working directory is not searched anymore for profiles or
163 - The current working directory is not searched anymore for profiles or
159 configurations files.
164 configurations files.
160 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
165 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
161 the list of fixed CVE. This is informational only.
166 the list of fixed CVE. This is informational only.
162
167
163 Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__
168 Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__
164
169
165
170
166 .. _version 8.0:
171 .. _version 8.0:
167
172
168 IPython 8.0
173 IPython 8.0
169 -----------
174 -----------
170
175
171 IPython 8.0 is bringing a large number of new features and improvements to both the
176 IPython 8.0 is bringing a large number of new features and improvements to both the
172 user of the terminal and of the kernel via Jupyter. The removal of compatibility
177 user of the terminal and of the kernel via Jupyter. The removal of compatibility
173 with older version of Python is also the opportunity to do a couple of
178 with older version of Python is also the opportunity to do a couple of
174 performance improvements in particular with respect to startup time.
179 performance improvements in particular with respect to startup time.
175 The 8.x branch started diverging from its predecessor around IPython 7.12
180 The 8.x branch started diverging from its predecessor around IPython 7.12
176 (January 2020).
181 (January 2020).
177
182
178 This release contains 250+ pull requests, in addition to many of the features
183 This release contains 250+ pull requests, in addition to many of the features
179 and backports that have made it to the 7.x branch. Please see the
184 and backports that have made it to the 7.x branch. Please see the
180 `8.0 milestone <https://github.com/ipython/ipython/milestone/73?closed=1>`__ for the full list of pull requests.
185 `8.0 milestone <https://github.com/ipython/ipython/milestone/73?closed=1>`__ for the full list of pull requests.
181
186
182 Please feel free to send pull requests to updates those notes after release,
187 Please feel free to send pull requests to updates those notes after release,
183 I have likely forgotten a few things reviewing 250+ PRs.
188 I have likely forgotten a few things reviewing 250+ PRs.
184
189
185 Dependencies changes/downstream packaging
190 Dependencies changes/downstream packaging
186 -----------------------------------------
191 -----------------------------------------
187
192
188 Most of our building steps have been changed to be (mostly) declarative
193 Most of our building steps have been changed to be (mostly) declarative
189 and follow PEP 517. We are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
194 and follow PEP 517. We are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
190 looking for help to do so.
195 looking for help to do so.
191
196
192 - minimum supported ``traitlets`` version is now 5+
197 - minimum supported ``traitlets`` version is now 5+
193 - we now require ``stack_data``
198 - we now require ``stack_data``
194 - minimal Python is now 3.8
199 - minimal Python is now 3.8
195 - ``nose`` is not a testing requirement anymore
200 - ``nose`` is not a testing requirement anymore
196 - ``pytest`` replaces nose.
201 - ``pytest`` replaces nose.
197 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
202 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
198 - minimum officially support ``numpy`` version has been bumped, but this should
203 - minimum officially support ``numpy`` version has been bumped, but this should
199 not have much effect on packaging.
204 not have much effect on packaging.
200
205
201
206
202 Deprecation and removal
207 Deprecation and removal
203 -----------------------
208 -----------------------
204
209
205 We removed almost all features, arguments, functions, and modules that were
210 We removed almost all features, arguments, functions, and modules that were
206 marked as deprecated between IPython 1.0 and 5.0. As a reminder, 5.0 was released
211 marked as deprecated between IPython 1.0 and 5.0. As a reminder, 5.0 was released
207 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in May 2020.
212 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in May 2020.
208 The few remaining deprecated features we left have better deprecation warnings
213 The few remaining deprecated features we left have better deprecation warnings
209 or have been turned into explicit errors for better error messages.
214 or have been turned into explicit errors for better error messages.
210
215
211 I will use this occasion to add the following requests to anyone emitting a
216 I will use this occasion to add the following requests to anyone emitting a
212 deprecation warning:
217 deprecation warning:
213
218
214 - Please add at least ``stacklevel=2`` so that the warning is emitted into the
219 - Please add at least ``stacklevel=2`` so that the warning is emitted into the
215 caller context, and not the callee one.
220 caller context, and not the callee one.
216 - Please add **since which version** something is deprecated.
221 - Please add **since which version** something is deprecated.
217
222
218 As a side note, it is much easier to conditionally compare version
223 As a side note, it is much easier to conditionally compare version
219 numbers rather than using ``try/except`` when functionality changes with a version.
224 numbers rather than using ``try/except`` when functionality changes with a version.
220
225
221 I won't list all the removed features here, but modules like ``IPython.kernel``,
226 I won't list all the removed features here, but modules like ``IPython.kernel``,
222 which was just a shim module around ``ipykernel`` for the past 8 years, have been
227 which was just a shim module around ``ipykernel`` for the past 8 years, have been
223 removed, and so many other similar things that pre-date the name **Jupyter**
228 removed, and so many other similar things that pre-date the name **Jupyter**
224 itself.
229 itself.
225
230
226 We no longer need to add ``IPython.extensions`` to the PYTHONPATH because that is being
231 We no longer need to add ``IPython.extensions`` to the PYTHONPATH because that is being
227 handled by ``load_extension``.
232 handled by ``load_extension``.
228
233
229 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
234 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
230 other packages and no longer need to be inside IPython.
235 other packages and no longer need to be inside IPython.
231
236
232
237
233 Documentation
238 Documentation
234 -------------
239 -------------
235
240
236 The majority of our docstrings have now been reformatted and automatically fixed by
241 The majority of our docstrings have now been reformatted and automatically fixed by
237 the experimental `VΓ©lin <https://pypi.org/project/velin/>`_ project to conform
242 the experimental `VΓ©lin <https://pypi.org/project/velin/>`_ project to conform
238 to numpydoc.
243 to numpydoc.
239
244
240 Type annotations
245 Type annotations
241 ----------------
246 ----------------
242
247
243 While IPython itself is highly dynamic and can't be completely typed, many of
248 While IPython itself is highly dynamic and can't be completely typed, many of
244 the functions now have type annotations, and part of the codebase is now checked
249 the functions now have type annotations, and part of the codebase is now checked
245 by mypy.
250 by mypy.
246
251
247
252
248 Featured changes
253 Featured changes
249 ----------------
254 ----------------
250
255
251 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
256 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
252 Please note as well that many features have been added in the 7.x branch as well
257 Please note as well that many features have been added in the 7.x branch as well
253 (and hence why you want to read the 7.x what's new notes), in particular
258 (and hence why you want to read the 7.x what's new notes), in particular
254 features contributed by QuantStack (with respect to debugger protocol and Xeus
259 features contributed by QuantStack (with respect to debugger protocol and Xeus
255 Python), as well as many debugger features that I was pleased to implement as
260 Python), as well as many debugger features that I was pleased to implement as
256 part of my work at QuanSight and sponsored by DE Shaw.
261 part of my work at QuanSight and sponsored by DE Shaw.
257
262
258 Traceback improvements
263 Traceback improvements
259 ~~~~~~~~~~~~~~~~~~~~~~
264 ~~~~~~~~~~~~~~~~~~~~~~
260
265
261 Previously, error tracebacks for errors happening in code cells were showing a
266 Previously, error tracebacks for errors happening in code cells were showing a
262 hash, the one used for compiling the Python AST::
267 hash, the one used for compiling the Python AST::
263
268
264 In [1]: def foo():
269 In [1]: def foo():
265 ...: return 3 / 0
270 ...: return 3 / 0
266 ...:
271 ...:
267
272
268 In [2]: foo()
273 In [2]: foo()
269 ---------------------------------------------------------------------------
274 ---------------------------------------------------------------------------
270 ZeroDivisionError Traceback (most recent call last)
275 ZeroDivisionError Traceback (most recent call last)
271 <ipython-input-2-c19b6d9633cf> in <module>
276 <ipython-input-2-c19b6d9633cf> in <module>
272 ----> 1 foo()
277 ----> 1 foo()
273
278
274 <ipython-input-1-1595a74c32d5> in foo()
279 <ipython-input-1-1595a74c32d5> in foo()
275 1 def foo():
280 1 def foo():
276 ----> 2 return 3 / 0
281 ----> 2 return 3 / 0
277 3
282 3
278
283
279 ZeroDivisionError: division by zero
284 ZeroDivisionError: division by zero
280
285
281 The error traceback is now correctly formatted, showing the cell number in which the error happened::
286 The error traceback is now correctly formatted, showing the cell number in which the error happened::
282
287
283 In [1]: def foo():
288 In [1]: def foo():
284 ...: return 3 / 0
289 ...: return 3 / 0
285 ...:
290 ...:
286
291
287 Input In [2]: foo()
292 Input In [2]: foo()
288 ---------------------------------------------------------------------------
293 ---------------------------------------------------------------------------
289 ZeroDivisionError Traceback (most recent call last)
294 ZeroDivisionError Traceback (most recent call last)
290 input In [2], in <module>
295 input In [2], in <module>
291 ----> 1 foo()
296 ----> 1 foo()
292
297
293 Input In [1], in foo()
298 Input In [1], in foo()
294 1 def foo():
299 1 def foo():
295 ----> 2 return 3 / 0
300 ----> 2 return 3 / 0
296
301
297 ZeroDivisionError: division by zero
302 ZeroDivisionError: division by zero
298
303
299 The ``stack_data`` package has been integrated, which provides smarter information in the traceback;
304 The ``stack_data`` package has been integrated, which provides smarter information in the traceback;
300 in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors.
305 in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors.
301
306
302 For example in the following snippet::
307 For example in the following snippet::
303
308
304 def foo(i):
309 def foo(i):
305 x = [[[0]]]
310 x = [[[0]]]
306 return x[0][i][0]
311 return x[0][i][0]
307
312
308
313
309 def bar():
314 def bar():
310 return foo(0) + foo(
315 return foo(0) + foo(
311 1
316 1
312 ) + foo(2)
317 ) + foo(2)
313
318
314
319
315 calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
320 calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
316 and IPython 8.0 is capable of telling you where the index error occurs::
321 and IPython 8.0 is capable of telling you where the index error occurs::
317
322
318
323
319 IndexError
324 IndexError
320 Input In [2], in <module>
325 Input In [2], in <module>
321 ----> 1 bar()
326 ----> 1 bar()
322 ^^^^^
327 ^^^^^
323
328
324 Input In [1], in bar()
329 Input In [1], in bar()
325 6 def bar():
330 6 def bar():
326 ----> 7 return foo(0) + foo(
331 ----> 7 return foo(0) + foo(
327 ^^^^
332 ^^^^
328 8 1
333 8 1
329 ^^^^^^^^
334 ^^^^^^^^
330 9 ) + foo(2)
335 9 ) + foo(2)
331 ^^^^
336 ^^^^
332
337
333 Input In [1], in foo(i)
338 Input In [1], in foo(i)
334 1 def foo(i):
339 1 def foo(i):
335 2 x = [[[0]]]
340 2 x = [[[0]]]
336 ----> 3 return x[0][i][0]
341 ----> 3 return x[0][i][0]
337 ^^^^^^^
342 ^^^^^^^
338
343
339 The corresponding locations marked here with ``^`` will show up highlighted in
344 The corresponding locations marked here with ``^`` will show up highlighted in
340 the terminal and notebooks.
345 the terminal and notebooks.
341
346
342 Finally, a colon ``::`` and line number is appended after a filename in
347 Finally, a colon ``::`` and line number is appended after a filename in
343 traceback::
348 traceback::
344
349
345
350
346 ZeroDivisionError Traceback (most recent call last)
351 ZeroDivisionError Traceback (most recent call last)
347 File ~/error.py:4, in <module>
352 File ~/error.py:4, in <module>
348 1 def f():
353 1 def f():
349 2 1/0
354 2 1/0
350 ----> 4 f()
355 ----> 4 f()
351
356
352 File ~/error.py:2, in f()
357 File ~/error.py:2, in f()
353 1 def f():
358 1 def f():
354 ----> 2 1/0
359 ----> 2 1/0
355
360
356 Many terminals and editors have integrations enabling you to directly jump to the
361 Many terminals and editors have integrations enabling you to directly jump to the
357 relevant file/line when this syntax is used, so this small addition may have a high
362 relevant file/line when this syntax is used, so this small addition may have a high
358 impact on productivity.
363 impact on productivity.
359
364
360
365
361 Autosuggestions
366 Autosuggestions
362 ~~~~~~~~~~~~~~~
367 ~~~~~~~~~~~~~~~
363
368
364 Autosuggestion is a very useful feature available in `fish <https://fishshell.com/>`__, `zsh <https://en.wikipedia.org/wiki/Z_shell>`__, and `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#auto-suggestion>`__.
369 Autosuggestion is a very useful feature available in `fish <https://fishshell.com/>`__, `zsh <https://en.wikipedia.org/wiki/Z_shell>`__, and `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#auto-suggestion>`__.
365
370
366 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
371 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
367 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
372 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
368
373
369 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
374 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
370 or right arrow as described below.
375 or right arrow as described below.
371
376
372 1. Start ipython
377 1. Start ipython
373
378
374 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
379 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
375
380
376 2. Run ``print("hello")``
381 2. Run ``print("hello")``
377
382
378 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
383 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
379
384
380 3. start typing ``print`` again to see the autosuggestion
385 3. start typing ``print`` again to see the autosuggestion
381
386
382 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
387 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
383
388
384 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
389 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
385
390
386 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
391 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
387
392
388 You can also complete word by word:
393 You can also complete word by word:
389
394
390 1. Run ``def say_hello(): print("hello")``
395 1. Run ``def say_hello(): print("hello")``
391
396
392 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
397 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
393
398
394 2. Start typing the first letter if ``def`` to see the autosuggestion
399 2. Start typing the first letter if ``def`` to see the autosuggestion
395
400
396 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
401 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
397
402
398 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
403 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
399
404
400 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
405 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
401
406
402 Importantly, this feature does not interfere with tab completion:
407 Importantly, this feature does not interfere with tab completion:
403
408
404 1. After running ``def say_hello(): print("hello")``, press d
409 1. After running ``def say_hello(): print("hello")``, press d
405
410
406 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
411 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
407
412
408 2. Press Tab to start tab completion
413 2. Press Tab to start tab completion
409
414
410 .. image:: ../_images/8.0/auto_suggest_d_completions.png
415 .. image:: ../_images/8.0/auto_suggest_d_completions.png
411
416
412 3A. Press Tab again to select the first option
417 3A. Press Tab again to select the first option
413
418
414 .. image:: ../_images/8.0/auto_suggest_def_completions.png
419 .. image:: ../_images/8.0/auto_suggest_def_completions.png
415
420
416 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
421 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
417
422
418 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
423 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
419
424
420 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
425 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
421
426
422 .. image:: ../_images/8.0/auto_suggest_match_parens.png
427 .. image:: ../_images/8.0/auto_suggest_match_parens.png
423
428
424
429
425 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
430 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
426
431
427 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
432 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
428 - To use these shortcuts in vi insert mode, you will have to create `custom keybindings in your config.py <https://github.com/mskar/setup/commit/2892fcee46f9f80ef7788f0749edc99daccc52f4/>`__.
433 - To use these shortcuts in vi insert mode, you will have to create `custom keybindings in your config.py <https://github.com/mskar/setup/commit/2892fcee46f9f80ef7788f0749edc99daccc52f4/>`__.
429
434
430
435
431 Show pinfo information in ipdb using "?" and "??"
436 Show pinfo information in ipdb using "?" and "??"
432 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
437 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
433
438
434 In IPDB, it is now possible to show the information about an object using "?"
439 In IPDB, it is now possible to show the information about an object using "?"
435 and "??", in much the same way that it can be done when using the IPython prompt::
440 and "??", in much the same way that it can be done when using the IPython prompt::
436
441
437 ipdb> partial?
442 ipdb> partial?
438 Init signature: partial(self, /, *args, **kwargs)
443 Init signature: partial(self, /, *args, **kwargs)
439 Docstring:
444 Docstring:
440 partial(func, *args, **keywords) - new function with partial application
445 partial(func, *args, **keywords) - new function with partial application
441 of the given arguments and keywords.
446 of the given arguments and keywords.
442 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
447 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
443 Type: type
448 Type: type
444 Subclasses:
449 Subclasses:
445
450
446 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
451 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
447
452
448
453
449 Autoreload 3 feature
454 Autoreload 3 feature
450 ~~~~~~~~~~~~~~~~~~~~
455 ~~~~~~~~~~~~~~~~~~~~
451
456
452 Example: When an IPython session is run with the 'autoreload' extension loaded,
457 Example: When an IPython session is run with the 'autoreload' extension loaded,
453 you will now have the option '3' to select, which means the following:
458 you will now have the option '3' to select, which means the following:
454
459
455 1. replicate all functionality from option 2
460 1. replicate all functionality from option 2
456 2. autoload all new funcs/classes/enums/globals from the module when they are added
461 2. autoload all new funcs/classes/enums/globals from the module when they are added
457 3. autoload all newly imported funcs/classes/enums/globals from external modules
462 3. autoload all newly imported funcs/classes/enums/globals from external modules
458
463
459 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``.
464 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``.
460
465
461 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
466 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
462
467
463 Auto formatting with black in the CLI
468 Auto formatting with black in the CLI
464 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
469 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
465
470
466 This feature was present in 7.x, but disabled by default.
471 This feature was present in 7.x, but disabled by default.
467
472
468 In 8.0, input was automatically reformatted with Black when black was installed.
473 In 8.0, input was automatically reformatted with Black when black was installed.
469 This feature has been reverted for the time being.
474 This feature has been reverted for the time being.
470 You can re-enable it by setting ``TerminalInteractiveShell.autoformatter`` to ``"black"``
475 You can re-enable it by setting ``TerminalInteractiveShell.autoformatter`` to ``"black"``
471
476
472 History Range Glob feature
477 History Range Glob feature
473 ~~~~~~~~~~~~~~~~~~~~~~~~~~
478 ~~~~~~~~~~~~~~~~~~~~~~~~~~
474
479
475 Previously, when using ``%history``, users could specify either
480 Previously, when using ``%history``, users could specify either
476 a range of sessions and lines, for example:
481 a range of sessions and lines, for example:
477
482
478 .. code-block:: python
483 .. code-block:: python
479
484
480 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
485 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
481 # to the fifth line of 6 sessions ago.``
486 # to the fifth line of 6 sessions ago.``
482
487
483 Or users could specify a glob pattern:
488 Or users could specify a glob pattern:
484
489
485 .. code-block:: python
490 .. code-block:: python
486
491
487 -g <pattern> # glob ALL history for the specified pattern.
492 -g <pattern> # glob ALL history for the specified pattern.
488
493
489 However users could *not* specify both.
494 However users could *not* specify both.
490
495
491 If a user *did* specify both a range and a glob pattern,
496 If a user *did* specify both a range and a glob pattern,
492 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
497 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
493
498
494 With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history.
499 With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history.
495
500
496 Don't start a multi-line cell with sunken parenthesis
501 Don't start a multi-line cell with sunken parenthesis
497 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
502 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
498
503
499 From now on, IPython will not ask for the next line of input when given a single
504 From now on, IPython will not ask for the next line of input when given a single
500 line with more closing than opening brackets. For example, this means that if
505 line with more closing than opening brackets. For example, this means that if
501 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
506 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
502 the ``...:`` prompt continuation.
507 the ``...:`` prompt continuation.
503
508
504 IPython shell for ipdb interact
509 IPython shell for ipdb interact
505 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
510 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
506
511
507 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
512 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
508
513
509 Automatic Vi prompt stripping
514 Automatic Vi prompt stripping
510 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
515 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
511
516
512 When pasting code into IPython, it will strip the leading prompt characters if
517 When pasting code into IPython, it will strip the leading prompt characters if
513 there are any. For example, you can paste the following code into the console -
518 there are any. For example, you can paste the following code into the console -
514 it will still work, even though each line is prefixed with prompts (`In`,
519 it will still work, even though each line is prefixed with prompts (`In`,
515 `Out`)::
520 `Out`)::
516
521
517 In [1]: 2 * 2 == 4
522 In [1]: 2 * 2 == 4
518 Out[1]: True
523 Out[1]: True
519
524
520 In [2]: print("This still works as pasted")
525 In [2]: print("This still works as pasted")
521
526
522
527
523 Previously, this was not the case for the Vi-mode prompts::
528 Previously, this was not the case for the Vi-mode prompts::
524
529
525 In [1]: [ins] In [13]: 2 * 2 == 4
530 In [1]: [ins] In [13]: 2 * 2 == 4
526 ...: Out[13]: True
531 ...: Out[13]: True
527 ...:
532 ...:
528 File "<ipython-input-1-727bb88eaf33>", line 1
533 File "<ipython-input-1-727bb88eaf33>", line 1
529 [ins] In [13]: 2 * 2 == 4
534 [ins] In [13]: 2 * 2 == 4
530 ^
535 ^
531 SyntaxError: invalid syntax
536 SyntaxError: invalid syntax
532
537
533 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
538 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
534 skipped just as the normal ``In`` would be.
539 skipped just as the normal ``In`` would be.
535
540
536 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
541 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
537 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
542 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
538
543
539 Empty History Ranges
544 Empty History Ranges
540 ~~~~~~~~~~~~~~~~~~~~
545 ~~~~~~~~~~~~~~~~~~~~
541
546
542 A number of magics that take history ranges can now be used with an empty
547 A number of magics that take history ranges can now be used with an empty
543 range. These magics are:
548 range. These magics are:
544
549
545 * ``%save``
550 * ``%save``
546 * ``%load``
551 * ``%load``
547 * ``%pastebin``
552 * ``%pastebin``
548 * ``%pycat``
553 * ``%pycat``
549
554
550 Using them this way will make them take the history of the current session up
555 Using them this way will make them take the history of the current session up
551 to the point of the magic call (such that the magic itself will not be
556 to the point of the magic call (such that the magic itself will not be
552 included).
557 included).
553
558
554 Therefore it is now possible to save the whole history to a file using
559 Therefore it is now possible to save the whole history to a file using
555 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
560 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
556 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
561 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
557 ``%pastebin``, or view the whole thing syntax-highlighted with a single
562 ``%pastebin``, or view the whole thing syntax-highlighted with a single
558 ``%pycat``.
563 ``%pycat``.
559
564
560
565
561 Windows timing implementation: Switch to process_time
566 Windows timing implementation: Switch to process_time
562 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
567 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
563 Timing on Windows, for example with ``%%time``, was changed from being based on ``time.perf_counter``
568 Timing on Windows, for example with ``%%time``, was changed from being based on ``time.perf_counter``
564 (which counted time even when the process was sleeping) to being based on ``time.process_time`` instead
569 (which counted time even when the process was sleeping) to being based on ``time.process_time`` instead
565 (which only counts CPU time). This brings it closer to the behavior on Linux. See :ghpull:`12984`.
570 (which only counts CPU time). This brings it closer to the behavior on Linux. See :ghpull:`12984`.
566
571
567 Miscellaneous
572 Miscellaneous
568 ~~~~~~~~~~~~~
573 ~~~~~~~~~~~~~
569 - Non-text formatters are not disabled in the terminal, which should simplify
574 - Non-text formatters are not disabled in the terminal, which should simplify
570 writing extensions displaying images or other mimetypes in supporting terminals.
575 writing extensions displaying images or other mimetypes in supporting terminals.
571 :ghpull:`12315`
576 :ghpull:`12315`
572 - It is now possible to automatically insert matching brackets in Terminal IPython using the
577 - It is now possible to automatically insert matching brackets in Terminal IPython using the
573 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
578 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
574 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`.
579 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`.
575 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
580 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
576 - ``%/%%timeit`` magic now adds a comma every thousands to make reading a long number easier :ghpull:`13379`
581 - ``%/%%timeit`` magic now adds a comma every thousands to make reading a long number easier :ghpull:`13379`
577 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
582 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
578 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
583 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
579 - The debugger now has a persistent history, which should make it less
584 - The debugger now has a persistent history, which should make it less
580 annoying to retype commands :ghpull:`13246`
585 annoying to retype commands :ghpull:`13246`
581 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing. We
586 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing. We
582 now warn users if they use one of those commands. :ghpull:`12954`
587 now warn users if they use one of those commands. :ghpull:`12954`
583 - Make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
588 - Make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
584
589
585 Re-added support for XDG config directories
590 Re-added support for XDG config directories
586 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
591 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
587
592
588 XDG support through the years comes and goes. There is a tension between having
593 XDG support through the years comes and goes. There is a tension between having
589 an identical location for configuration in all platforms versus having simple instructions.
594 an identical location for configuration in all platforms versus having simple instructions.
590 After initial failures a couple of years ago, IPython was modified to automatically migrate XDG
595 After initial failures a couple of years ago, IPython was modified to automatically migrate XDG
591 config files back into ``~/.ipython``. That migration code has now been removed.
596 config files back into ``~/.ipython``. That migration code has now been removed.
592 IPython now checks the XDG locations, so if you _manually_ move your config
597 IPython now checks the XDG locations, so if you _manually_ move your config
593 files to your preferred location, IPython will not move them back.
598 files to your preferred location, IPython will not move them back.
594
599
595
600
596 Preparing for Python 3.10
601 Preparing for Python 3.10
597 -------------------------
602 -------------------------
598
603
599 To prepare for Python 3.10, we have started working on removing reliance and
604 To prepare for Python 3.10, we have started working on removing reliance and
600 any dependency that is not compatible with Python 3.10. This includes migrating our
605 any dependency that is not compatible with Python 3.10. This includes migrating our
601 test suite to pytest and starting to remove nose. This also means that the
606 test suite to pytest and starting to remove nose. This also means that the
602 ``iptest`` command is now gone and all testing is via pytest.
607 ``iptest`` command is now gone and all testing is via pytest.
603
608
604 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
609 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
605 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
610 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
606 who did a fantastic job at updating our code base, migrating to pytest, pushing
611 who did a fantastic job at updating our code base, migrating to pytest, pushing
607 our coverage, and fixing a large number of bugs. I highly recommend contacting
612 our coverage, and fixing a large number of bugs. I highly recommend contacting
608 them if you need help with C++ and Python projects.
613 them if you need help with C++ and Python projects.
609
614
610 You can find all relevant issues and PRs with the SDG 2021 tag `<https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+>`__
615 You can find all relevant issues and PRs with the SDG 2021 tag `<https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+>`__
611
616
612 Removing support for older Python versions
617 Removing support for older Python versions
613 ------------------------------------------
618 ------------------------------------------
614
619
615
620
616 We are removing support for Python up through 3.7, allowing internal code to use the more
621 We are removing support for Python up through 3.7, allowing internal code to use the more
617 efficient ``pathlib`` and to make better use of type annotations.
622 efficient ``pathlib`` and to make better use of type annotations.
618
623
619 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
624 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
620 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
625 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
621
626
622
627
623 We had about 34 PRs only to update some logic to update some functions from managing strings to
628 We had about 34 PRs only to update some logic to update some functions from managing strings to
624 using Pathlib.
629 using Pathlib.
625
630
626 The completer has also seen significant updates and now makes use of newer Jedi APIs,
631 The completer has also seen significant updates and now makes use of newer Jedi APIs,
627 offering faster and more reliable tab completion.
632 offering faster and more reliable tab completion.
628
633
629 Misc Statistics
634 Misc Statistics
630 ---------------
635 ---------------
631
636
632 Here are some numbers::
637 Here are some numbers::
633
638
634 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
639 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
635 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
640 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
636
641
637 $ git diff --stat 7.x...master | tail -1
642 $ git diff --stat 7.x...master | tail -1
638 340 files changed, 13399 insertions(+), 12421 deletions(-)
643 340 files changed, 13399 insertions(+), 12421 deletions(-)
639
644
640 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges (to not bias toward
645 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges (to not bias toward
641 maintainers pushing buttons).::
646 maintainers pushing buttons).::
642
647
643 $ git shortlog -s --no-merges 7.x...master | sort -nr
648 $ git shortlog -s --no-merges 7.x...master | sort -nr
644 535 Matthias Bussonnier
649 535 Matthias Bussonnier
645 86 Nikita Kniazev
650 86 Nikita Kniazev
646 69 Blazej Michalik
651 69 Blazej Michalik
647 49 Samuel Gaist
652 49 Samuel Gaist
648 27 Itamar Turner-Trauring
653 27 Itamar Turner-Trauring
649 18 Spas Kalaydzhisyki
654 18 Spas Kalaydzhisyki
650 17 Thomas Kluyver
655 17 Thomas Kluyver
651 17 Quentin Peter
656 17 Quentin Peter
652 17 James Morris
657 17 James Morris
653 17 Artur Svistunov
658 17 Artur Svistunov
654 15 Bart Skowron
659 15 Bart Skowron
655 14 Alex Hall
660 14 Alex Hall
656 13 rushabh-v
661 13 rushabh-v
657 13 Terry Davis
662 13 Terry Davis
658 13 Benjamin Ragan-Kelley
663 13 Benjamin Ragan-Kelley
659 8 martinRenou
664 8 martinRenou
660 8 farisachugthai
665 8 farisachugthai
661 7 dswij
666 7 dswij
662 7 Gal B
667 7 Gal B
663 7 Corentin Cadiou
668 7 Corentin Cadiou
664 6 yuji96
669 6 yuji96
665 6 Martin Skarzynski
670 6 Martin Skarzynski
666 6 Justin Palmer
671 6 Justin Palmer
667 6 Daniel Goldfarb
672 6 Daniel Goldfarb
668 6 Ben Greiner
673 6 Ben Greiner
669 5 Sammy Al Hashemi
674 5 Sammy Al Hashemi
670 5 Paul Ivanov
675 5 Paul Ivanov
671 5 Inception95
676 5 Inception95
672 5 Eyenpi
677 5 Eyenpi
673 5 Douglas Blank
678 5 Douglas Blank
674 5 Coco Mishra
679 5 Coco Mishra
675 5 Bibo Hao
680 5 Bibo Hao
676 5 AndrΓ© A. Gomes
681 5 AndrΓ© A. Gomes
677 5 Ahmed Fasih
682 5 Ahmed Fasih
678 4 takuya fujiwara
683 4 takuya fujiwara
679 4 palewire
684 4 palewire
680 4 Thomas A Caswell
685 4 Thomas A Caswell
681 4 Talley Lambert
686 4 Talley Lambert
682 4 Scott Sanderson
687 4 Scott Sanderson
683 4 Ram Rachum
688 4 Ram Rachum
684 4 Nick Muoh
689 4 Nick Muoh
685 4 Nathan Goldbaum
690 4 Nathan Goldbaum
686 4 Mithil Poojary
691 4 Mithil Poojary
687 4 Michael T
692 4 Michael T
688 4 Jakub Klus
693 4 Jakub Klus
689 4 Ian Castleden
694 4 Ian Castleden
690 4 Eli Rykoff
695 4 Eli Rykoff
691 4 Ashwin Vishnu
696 4 Ashwin Vishnu
692 3 谭九鼎
697 3 谭九鼎
693 3 sleeping
698 3 sleeping
694 3 Sylvain Corlay
699 3 Sylvain Corlay
695 3 Peter Corke
700 3 Peter Corke
696 3 Paul Bissex
701 3 Paul Bissex
697 3 Matthew Feickert
702 3 Matthew Feickert
698 3 Fernando Perez
703 3 Fernando Perez
699 3 Eric Wieser
704 3 Eric Wieser
700 3 Daniel Mietchen
705 3 Daniel Mietchen
701 3 Aditya Sathe
706 3 Aditya Sathe
702 3 007vedant
707 3 007vedant
703 2 rchiodo
708 2 rchiodo
704 2 nicolaslazo
709 2 nicolaslazo
705 2 luttik
710 2 luttik
706 2 gorogoroumaru
711 2 gorogoroumaru
707 2 foobarbyte
712 2 foobarbyte
708 2 bar-hen
713 2 bar-hen
709 2 Theo Ouzhinski
714 2 Theo Ouzhinski
710 2 Strawkage
715 2 Strawkage
711 2 Samreen Zarroug
716 2 Samreen Zarroug
712 2 Pete Blois
717 2 Pete Blois
713 2 Meysam Azad
718 2 Meysam Azad
714 2 Matthieu Ancellin
719 2 Matthieu Ancellin
715 2 Mark Schmitz
720 2 Mark Schmitz
716 2 Maor Kleinberger
721 2 Maor Kleinberger
717 2 MRCWirtz
722 2 MRCWirtz
718 2 Lumir Balhar
723 2 Lumir Balhar
719 2 Julien Rabinow
724 2 Julien Rabinow
720 2 Juan Luis Cano RodrΓ­guez
725 2 Juan Luis Cano RodrΓ­guez
721 2 Joyce Er
726 2 Joyce Er
722 2 Jakub
727 2 Jakub
723 2 Faris A Chugthai
728 2 Faris A Chugthai
724 2 Ethan Madden
729 2 Ethan Madden
725 2 Dimitri Papadopoulos
730 2 Dimitri Papadopoulos
726 2 Diego Fernandez
731 2 Diego Fernandez
727 2 Daniel Shimon
732 2 Daniel Shimon
728 2 Coco Bennett
733 2 Coco Bennett
729 2 Carlos Cordoba
734 2 Carlos Cordoba
730 2 Boyuan Liu
735 2 Boyuan Liu
731 2 BaoGiang HoangVu
736 2 BaoGiang HoangVu
732 2 Augusto
737 2 Augusto
733 2 Arthur Svistunov
738 2 Arthur Svistunov
734 2 Arthur Moreira
739 2 Arthur Moreira
735 2 Ali Nabipour
740 2 Ali Nabipour
736 2 Adam Hackbarth
741 2 Adam Hackbarth
737 1 richard
742 1 richard
738 1 linar-jether
743 1 linar-jether
739 1 lbennett
744 1 lbennett
740 1 juacrumar
745 1 juacrumar
741 1 gpotter2
746 1 gpotter2
742 1 digitalvirtuoso
747 1 digitalvirtuoso
743 1 dalthviz
748 1 dalthviz
744 1 Yonatan Goldschmidt
749 1 Yonatan Goldschmidt
745 1 Tomasz KΕ‚oczko
750 1 Tomasz KΕ‚oczko
746 1 Tobias Bengfort
751 1 Tobias Bengfort
747 1 Timur Kushukov
752 1 Timur Kushukov
748 1 Thomas
753 1 Thomas
749 1 Snir Broshi
754 1 Snir Broshi
750 1 Shao Yang Hong
755 1 Shao Yang Hong
751 1 Sanjana-03
756 1 Sanjana-03
752 1 Romulo Filho
757 1 Romulo Filho
753 1 Rodolfo Carvalho
758 1 Rodolfo Carvalho
754 1 Richard Shadrach
759 1 Richard Shadrach
755 1 Reilly Tucker Siemens
760 1 Reilly Tucker Siemens
756 1 Rakessh Roshan
761 1 Rakessh Roshan
757 1 Piers Titus van der Torren
762 1 Piers Titus van der Torren
758 1 PhanatosZou
763 1 PhanatosZou
759 1 Pavel Safronov
764 1 Pavel Safronov
760 1 Paulo S. Costa
765 1 Paulo S. Costa
761 1 Paul McCarthy
766 1 Paul McCarthy
762 1 NotWearingPants
767 1 NotWearingPants
763 1 Naelson Douglas
768 1 Naelson Douglas
764 1 Michael Tiemann
769 1 Michael Tiemann
765 1 Matt Wozniski
770 1 Matt Wozniski
766 1 Markus Wageringel
771 1 Markus Wageringel
767 1 Marcus Wirtz
772 1 Marcus Wirtz
768 1 Marcio Mazza
773 1 Marcio Mazza
769 1 LumΓ­r 'Frenzy' Balhar
774 1 LumΓ­r 'Frenzy' Balhar
770 1 Lightyagami1
775 1 Lightyagami1
771 1 Leon Anavi
776 1 Leon Anavi
772 1 LeafyLi
777 1 LeafyLi
773 1 L0uisJ0shua
778 1 L0uisJ0shua
774 1 Kyle Cutler
779 1 Kyle Cutler
775 1 Krzysztof Cybulski
780 1 Krzysztof Cybulski
776 1 Kevin Kirsche
781 1 Kevin Kirsche
777 1 KIU Shueng Chuan
782 1 KIU Shueng Chuan
778 1 Jonathan Slenders
783 1 Jonathan Slenders
779 1 Jay Qi
784 1 Jay Qi
780 1 Jake VanderPlas
785 1 Jake VanderPlas
781 1 Iwan Briquemont
786 1 Iwan Briquemont
782 1 Hussaina Begum Nandyala
787 1 Hussaina Begum Nandyala
783 1 Gordon Ball
788 1 Gordon Ball
784 1 Gabriel Simonetto
789 1 Gabriel Simonetto
785 1 Frank Tobia
790 1 Frank Tobia
786 1 Erik
791 1 Erik
787 1 Elliott Sales de Andrade
792 1 Elliott Sales de Andrade
788 1 Daniel Hahler
793 1 Daniel Hahler
789 1 Dan Green-Leipciger
794 1 Dan Green-Leipciger
790 1 Dan Green
795 1 Dan Green
791 1 Damian Yurzola
796 1 Damian Yurzola
792 1 Coon, Ethan T
797 1 Coon, Ethan T
793 1 Carol Willing
798 1 Carol Willing
794 1 Brian Lee
799 1 Brian Lee
795 1 Brendan Gerrity
800 1 Brendan Gerrity
796 1 Blake Griffin
801 1 Blake Griffin
797 1 Bastian Ebeling
802 1 Bastian Ebeling
798 1 Bartosz Telenczuk
803 1 Bartosz Telenczuk
799 1 Ankitsingh6299
804 1 Ankitsingh6299
800 1 Andrew Port
805 1 Andrew Port
801 1 Andrew J. Hesford
806 1 Andrew J. Hesford
802 1 Albert Zhang
807 1 Albert Zhang
803 1 Adam Johnson
808 1 Adam Johnson
804
809
805 This does not, of course, represent non-code contributions, for which we are also grateful.
810 This does not, of course, represent non-code contributions, for which we are also grateful.
806
811
807
812
808 API Changes using Frappuccino
813 API Changes using Frappuccino
809 -----------------------------
814 -----------------------------
810
815
811 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
816 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
812
817
813
818
814 The following items are new in IPython 8.0 ::
819 The following items are new in IPython 8.0 ::
815
820
816 + IPython.core.async_helpers.get_asyncio_loop()
821 + IPython.core.async_helpers.get_asyncio_loop()
817 + IPython.core.completer.Dict
822 + IPython.core.completer.Dict
818 + IPython.core.completer.Pattern
823 + IPython.core.completer.Pattern
819 + IPython.core.completer.Sequence
824 + IPython.core.completer.Sequence
820 + IPython.core.completer.__skip_doctest__
825 + IPython.core.completer.__skip_doctest__
821 + IPython.core.debugger.Pdb.precmd(self, line)
826 + IPython.core.debugger.Pdb.precmd(self, line)
822 + IPython.core.debugger.__skip_doctest__
827 + IPython.core.debugger.__skip_doctest__
823 + IPython.core.display.__getattr__(name)
828 + IPython.core.display.__getattr__(name)
824 + IPython.core.display.warn
829 + IPython.core.display.warn
825 + IPython.core.display_functions
830 + IPython.core.display_functions
826 + IPython.core.display_functions.DisplayHandle
831 + IPython.core.display_functions.DisplayHandle
827 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
832 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
828 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
833 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
829 + IPython.core.display_functions.__all__
834 + IPython.core.display_functions.__all__
830 + IPython.core.display_functions.__builtins__
835 + IPython.core.display_functions.__builtins__
831 + IPython.core.display_functions.__cached__
836 + IPython.core.display_functions.__cached__
832 + IPython.core.display_functions.__doc__
837 + IPython.core.display_functions.__doc__
833 + IPython.core.display_functions.__file__
838 + IPython.core.display_functions.__file__
834 + IPython.core.display_functions.__loader__
839 + IPython.core.display_functions.__loader__
835 + IPython.core.display_functions.__name__
840 + IPython.core.display_functions.__name__
836 + IPython.core.display_functions.__package__
841 + IPython.core.display_functions.__package__
837 + IPython.core.display_functions.__spec__
842 + IPython.core.display_functions.__spec__
838 + IPython.core.display_functions.b2a_hex
843 + IPython.core.display_functions.b2a_hex
839 + IPython.core.display_functions.clear_output(wait=False)
844 + IPython.core.display_functions.clear_output(wait=False)
840 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
845 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
841 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
846 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
842 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
847 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
843 + IPython.core.extensions.BUILTINS_EXTS
848 + IPython.core.extensions.BUILTINS_EXTS
844 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
849 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
845 + IPython.core.interactiveshell.Callable
850 + IPython.core.interactiveshell.Callable
846 + IPython.core.interactiveshell.__annotations__
851 + IPython.core.interactiveshell.__annotations__
847 + IPython.core.ultratb.List
852 + IPython.core.ultratb.List
848 + IPython.core.ultratb.Tuple
853 + IPython.core.ultratb.Tuple
849 + IPython.lib.pretty.CallExpression
854 + IPython.lib.pretty.CallExpression
850 + IPython.lib.pretty.CallExpression.factory(name)
855 + IPython.lib.pretty.CallExpression.factory(name)
851 + IPython.lib.pretty.RawStringLiteral
856 + IPython.lib.pretty.RawStringLiteral
852 + IPython.lib.pretty.RawText
857 + IPython.lib.pretty.RawText
853 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
858 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
854 + IPython.terminal.embed.Set
859 + IPython.terminal.embed.Set
855
860
856 The following items have been removed (or moved to superclass)::
861 The following items have been removed (or moved to superclass)::
857
862
858 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
863 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
859 - IPython.core.completer.Sentinel
864 - IPython.core.completer.Sentinel
860 - IPython.core.completer.skip_doctest
865 - IPython.core.completer.skip_doctest
861 - IPython.core.debugger.Tracer
866 - IPython.core.debugger.Tracer
862 - IPython.core.display.DisplayHandle
867 - IPython.core.display.DisplayHandle
863 - IPython.core.display.DisplayHandle.display
868 - IPython.core.display.DisplayHandle.display
864 - IPython.core.display.DisplayHandle.update
869 - IPython.core.display.DisplayHandle.update
865 - IPython.core.display.b2a_hex
870 - IPython.core.display.b2a_hex
866 - IPython.core.display.clear_output
871 - IPython.core.display.clear_output
867 - IPython.core.display.display
872 - IPython.core.display.display
868 - IPython.core.display.publish_display_data
873 - IPython.core.display.publish_display_data
869 - IPython.core.display.update_display
874 - IPython.core.display.update_display
870 - IPython.core.excolors.Deprec
875 - IPython.core.excolors.Deprec
871 - IPython.core.excolors.ExceptionColors
876 - IPython.core.excolors.ExceptionColors
872 - IPython.core.history.warn
877 - IPython.core.history.warn
873 - IPython.core.hooks.late_startup_hook
878 - IPython.core.hooks.late_startup_hook
874 - IPython.core.hooks.pre_run_code_hook
879 - IPython.core.hooks.pre_run_code_hook
875 - IPython.core.hooks.shutdown_hook
880 - IPython.core.hooks.shutdown_hook
876 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
881 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
877 - IPython.core.interactiveshell.InteractiveShell.init_readline
882 - IPython.core.interactiveshell.InteractiveShell.init_readline
878 - IPython.core.interactiveshell.InteractiveShell.write
883 - IPython.core.interactiveshell.InteractiveShell.write
879 - IPython.core.interactiveshell.InteractiveShell.write_err
884 - IPython.core.interactiveshell.InteractiveShell.write_err
880 - IPython.core.interactiveshell.get_default_colors
885 - IPython.core.interactiveshell.get_default_colors
881 - IPython.core.interactiveshell.removed_co_newlocals
886 - IPython.core.interactiveshell.removed_co_newlocals
882 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
887 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
883 - IPython.core.magics.script.PIPE
888 - IPython.core.magics.script.PIPE
884 - IPython.core.prefilter.PrefilterManager.init_transformers
889 - IPython.core.prefilter.PrefilterManager.init_transformers
885 - IPython.core.release.classifiers
890 - IPython.core.release.classifiers
886 - IPython.core.release.description
891 - IPython.core.release.description
887 - IPython.core.release.keywords
892 - IPython.core.release.keywords
888 - IPython.core.release.long_description
893 - IPython.core.release.long_description
889 - IPython.core.release.name
894 - IPython.core.release.name
890 - IPython.core.release.platforms
895 - IPython.core.release.platforms
891 - IPython.core.release.url
896 - IPython.core.release.url
892 - IPython.core.ultratb.VerboseTB.format_records
897 - IPython.core.ultratb.VerboseTB.format_records
893 - IPython.core.ultratb.find_recursion
898 - IPython.core.ultratb.find_recursion
894 - IPython.core.ultratb.findsource
899 - IPython.core.ultratb.findsource
895 - IPython.core.ultratb.fix_frame_records_filenames
900 - IPython.core.ultratb.fix_frame_records_filenames
896 - IPython.core.ultratb.inspect_error
901 - IPython.core.ultratb.inspect_error
897 - IPython.core.ultratb.is_recursion_error
902 - IPython.core.ultratb.is_recursion_error
898 - IPython.core.ultratb.with_patch_inspect
903 - IPython.core.ultratb.with_patch_inspect
899 - IPython.external.__all__
904 - IPython.external.__all__
900 - IPython.external.__builtins__
905 - IPython.external.__builtins__
901 - IPython.external.__cached__
906 - IPython.external.__cached__
902 - IPython.external.__doc__
907 - IPython.external.__doc__
903 - IPython.external.__file__
908 - IPython.external.__file__
904 - IPython.external.__loader__
909 - IPython.external.__loader__
905 - IPython.external.__name__
910 - IPython.external.__name__
906 - IPython.external.__package__
911 - IPython.external.__package__
907 - IPython.external.__path__
912 - IPython.external.__path__
908 - IPython.external.__spec__
913 - IPython.external.__spec__
909 - IPython.kernel.KernelConnectionInfo
914 - IPython.kernel.KernelConnectionInfo
910 - IPython.kernel.__builtins__
915 - IPython.kernel.__builtins__
911 - IPython.kernel.__cached__
916 - IPython.kernel.__cached__
912 - IPython.kernel.__warningregistry__
917 - IPython.kernel.__warningregistry__
913 - IPython.kernel.pkg
918 - IPython.kernel.pkg
914 - IPython.kernel.protocol_version
919 - IPython.kernel.protocol_version
915 - IPython.kernel.protocol_version_info
920 - IPython.kernel.protocol_version_info
916 - IPython.kernel.src
921 - IPython.kernel.src
917 - IPython.kernel.version_info
922 - IPython.kernel.version_info
918 - IPython.kernel.warn
923 - IPython.kernel.warn
919 - IPython.lib.backgroundjobs
924 - IPython.lib.backgroundjobs
920 - IPython.lib.backgroundjobs.BackgroundJobBase
925 - IPython.lib.backgroundjobs.BackgroundJobBase
921 - IPython.lib.backgroundjobs.BackgroundJobBase.run
926 - IPython.lib.backgroundjobs.BackgroundJobBase.run
922 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
927 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
923 - IPython.lib.backgroundjobs.BackgroundJobExpr
928 - IPython.lib.backgroundjobs.BackgroundJobExpr
924 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
929 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
925 - IPython.lib.backgroundjobs.BackgroundJobFunc
930 - IPython.lib.backgroundjobs.BackgroundJobFunc
926 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
931 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
927 - IPython.lib.backgroundjobs.BackgroundJobManager
932 - IPython.lib.backgroundjobs.BackgroundJobManager
928 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
933 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
929 - IPython.lib.backgroundjobs.BackgroundJobManager.new
934 - IPython.lib.backgroundjobs.BackgroundJobManager.new
930 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
935 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
931 - IPython.lib.backgroundjobs.BackgroundJobManager.result
936 - IPython.lib.backgroundjobs.BackgroundJobManager.result
932 - IPython.lib.backgroundjobs.BackgroundJobManager.status
937 - IPython.lib.backgroundjobs.BackgroundJobManager.status
933 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
938 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
934 - IPython.lib.backgroundjobs.__builtins__
939 - IPython.lib.backgroundjobs.__builtins__
935 - IPython.lib.backgroundjobs.__cached__
940 - IPython.lib.backgroundjobs.__cached__
936 - IPython.lib.backgroundjobs.__doc__
941 - IPython.lib.backgroundjobs.__doc__
937 - IPython.lib.backgroundjobs.__file__
942 - IPython.lib.backgroundjobs.__file__
938 - IPython.lib.backgroundjobs.__loader__
943 - IPython.lib.backgroundjobs.__loader__
939 - IPython.lib.backgroundjobs.__name__
944 - IPython.lib.backgroundjobs.__name__
940 - IPython.lib.backgroundjobs.__package__
945 - IPython.lib.backgroundjobs.__package__
941 - IPython.lib.backgroundjobs.__spec__
946 - IPython.lib.backgroundjobs.__spec__
942 - IPython.lib.kernel.__builtins__
947 - IPython.lib.kernel.__builtins__
943 - IPython.lib.kernel.__cached__
948 - IPython.lib.kernel.__cached__
944 - IPython.lib.kernel.__doc__
949 - IPython.lib.kernel.__doc__
945 - IPython.lib.kernel.__file__
950 - IPython.lib.kernel.__file__
946 - IPython.lib.kernel.__loader__
951 - IPython.lib.kernel.__loader__
947 - IPython.lib.kernel.__name__
952 - IPython.lib.kernel.__name__
948 - IPython.lib.kernel.__package__
953 - IPython.lib.kernel.__package__
949 - IPython.lib.kernel.__spec__
954 - IPython.lib.kernel.__spec__
950 - IPython.lib.kernel.__warningregistry__
955 - IPython.lib.kernel.__warningregistry__
951 - IPython.paths.fs_encoding
956 - IPython.paths.fs_encoding
952 - IPython.terminal.debugger.DEFAULT_BUFFER
957 - IPython.terminal.debugger.DEFAULT_BUFFER
953 - IPython.terminal.debugger.cursor_in_leading_ws
958 - IPython.terminal.debugger.cursor_in_leading_ws
954 - IPython.terminal.debugger.emacs_insert_mode
959 - IPython.terminal.debugger.emacs_insert_mode
955 - IPython.terminal.debugger.has_selection
960 - IPython.terminal.debugger.has_selection
956 - IPython.terminal.debugger.vi_insert_mode
961 - IPython.terminal.debugger.vi_insert_mode
957 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
962 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
958 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
963 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
959 - IPython.testing.test
964 - IPython.testing.test
960 - IPython.utils.contexts.NoOpContext
965 - IPython.utils.contexts.NoOpContext
961 - IPython.utils.io.IOStream
966 - IPython.utils.io.IOStream
962 - IPython.utils.io.IOStream.close
967 - IPython.utils.io.IOStream.close
963 - IPython.utils.io.IOStream.write
968 - IPython.utils.io.IOStream.write
964 - IPython.utils.io.IOStream.writelines
969 - IPython.utils.io.IOStream.writelines
965 - IPython.utils.io.__warningregistry__
970 - IPython.utils.io.__warningregistry__
966 - IPython.utils.io.atomic_writing
971 - IPython.utils.io.atomic_writing
967 - IPython.utils.io.stderr
972 - IPython.utils.io.stderr
968 - IPython.utils.io.stdin
973 - IPython.utils.io.stdin
969 - IPython.utils.io.stdout
974 - IPython.utils.io.stdout
970 - IPython.utils.io.unicode_std_stream
975 - IPython.utils.io.unicode_std_stream
971 - IPython.utils.path.get_ipython_cache_dir
976 - IPython.utils.path.get_ipython_cache_dir
972 - IPython.utils.path.get_ipython_dir
977 - IPython.utils.path.get_ipython_dir
973 - IPython.utils.path.get_ipython_module_path
978 - IPython.utils.path.get_ipython_module_path
974 - IPython.utils.path.get_ipython_package_dir
979 - IPython.utils.path.get_ipython_package_dir
975 - IPython.utils.path.locate_profile
980 - IPython.utils.path.locate_profile
976 - IPython.utils.path.unquote_filename
981 - IPython.utils.path.unquote_filename
977 - IPython.utils.py3compat.PY2
982 - IPython.utils.py3compat.PY2
978 - IPython.utils.py3compat.PY3
983 - IPython.utils.py3compat.PY3
979 - IPython.utils.py3compat.buffer_to_bytes
984 - IPython.utils.py3compat.buffer_to_bytes
980 - IPython.utils.py3compat.builtin_mod_name
985 - IPython.utils.py3compat.builtin_mod_name
981 - IPython.utils.py3compat.cast_bytes
986 - IPython.utils.py3compat.cast_bytes
982 - IPython.utils.py3compat.getcwd
987 - IPython.utils.py3compat.getcwd
983 - IPython.utils.py3compat.isidentifier
988 - IPython.utils.py3compat.isidentifier
984 - IPython.utils.py3compat.u_format
989 - IPython.utils.py3compat.u_format
985
990
986 The following signatures differ between 7.x and 8.0::
991 The following signatures differ between 7.x and 8.0::
987
992
988 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
993 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
989 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
994 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
990
995
991 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
996 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
992 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
997 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
993
998
994 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
999 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
995 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
1000 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
996
1001
997 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
1002 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
998 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
1003 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
999
1004
1000 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
1005 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
1001 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
1006 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
1002
1007
1003 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
1008 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
1004 + IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True, omit_sections='()')
1009 + IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True, omit_sections='()')
1005
1010
1006 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
1011 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
1007 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
1012 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
1008
1013
1009 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
1014 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
1010 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
1015 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
1011
1016
1012 - IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, display_banner='None', global_ns='None', compile_flags='None')
1017 - IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, display_banner='None', global_ns='None', compile_flags='None')
1013 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
1018 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
1014
1019
1015 - IPython.terminal.embed.embed(**kwargs)
1020 - IPython.terminal.embed.embed(**kwargs)
1016 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
1021 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
1017
1022
1018 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
1023 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
1019 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
1024 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
1020
1025
1021 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
1026 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
1022 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
1027 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
1023
1028
1024 - IPython.utils.path.get_py_filename(name, force_win32='None')
1029 - IPython.utils.path.get_py_filename(name, force_win32='None')
1025 + IPython.utils.path.get_py_filename(name)
1030 + IPython.utils.path.get_py_filename(name)
1026
1031
1027 The following are new attributes (that might be inherited)::
1032 The following are new attributes (that might be inherited)::
1028
1033
1029 + IPython.core.completer.IPCompleter.unicode_names
1034 + IPython.core.completer.IPCompleter.unicode_names
1030 + IPython.core.debugger.InterruptiblePdb.precmd
1035 + IPython.core.debugger.InterruptiblePdb.precmd
1031 + IPython.core.debugger.Pdb.precmd
1036 + IPython.core.debugger.Pdb.precmd
1032 + IPython.core.ultratb.AutoFormattedTB.has_colors
1037 + IPython.core.ultratb.AutoFormattedTB.has_colors
1033 + IPython.core.ultratb.ColorTB.has_colors
1038 + IPython.core.ultratb.ColorTB.has_colors
1034 + IPython.core.ultratb.FormattedTB.has_colors
1039 + IPython.core.ultratb.FormattedTB.has_colors
1035 + IPython.core.ultratb.ListTB.has_colors
1040 + IPython.core.ultratb.ListTB.has_colors
1036 + IPython.core.ultratb.SyntaxTB.has_colors
1041 + IPython.core.ultratb.SyntaxTB.has_colors
1037 + IPython.core.ultratb.TBTools.has_colors
1042 + IPython.core.ultratb.TBTools.has_colors
1038 + IPython.core.ultratb.VerboseTB.has_colors
1043 + IPython.core.ultratb.VerboseTB.has_colors
1039 + IPython.terminal.debugger.TerminalPdb.do_interact
1044 + IPython.terminal.debugger.TerminalPdb.do_interact
1040 + IPython.terminal.debugger.TerminalPdb.precmd
1045 + IPython.terminal.debugger.TerminalPdb.precmd
1041
1046
1042 The following attribute/methods have been removed::
1047 The following attribute/methods have been removed::
1043
1048
1044 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
1049 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
1045 - IPython.core.ultratb.AutoFormattedTB.format_records
1050 - IPython.core.ultratb.AutoFormattedTB.format_records
1046 - IPython.core.ultratb.ColorTB.format_records
1051 - IPython.core.ultratb.ColorTB.format_records
1047 - IPython.core.ultratb.FormattedTB.format_records
1052 - IPython.core.ultratb.FormattedTB.format_records
1048 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
1053 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
1049 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
1054 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
1050 - IPython.terminal.embed.InteractiveShellEmbed.write
1055 - IPython.terminal.embed.InteractiveShellEmbed.write
1051 - IPython.terminal.embed.InteractiveShellEmbed.write_err
1056 - IPython.terminal.embed.InteractiveShellEmbed.write_err
1052 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
1057 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
1053 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
1058 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
1054 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
1059 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
1055 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
1060 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
1056 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
1061 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
1057 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
1062 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
1058 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
1063 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
1059 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
1064 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
General Comments 0
You need to be logged in to leave comments. Login now