##// END OF EJS Templates
Simplify InputSplitter by stripping out input_mode distinction
Thomas Kluyver -
Show More
@@ -247,8 +247,6 b' class InputSplitter(object):'
247 # synced to the source, so it can be queried at any time to obtain the code
247 # synced to the source, so it can be queried at any time to obtain the code
248 # object; it will be None if the source doesn't compile to valid Python.
248 # object; it will be None if the source doesn't compile to valid Python.
249 code = None
249 code = None
250 # Input mode
251 input_mode = 'line'
252
250
253 # Private attributes
251 # Private attributes
254
252
@@ -261,32 +259,12 b' class InputSplitter(object):'
261 # Boolean indicating whether the current block is complete
259 # Boolean indicating whether the current block is complete
262 _is_complete = None
260 _is_complete = None
263
261
264 def __init__(self, input_mode=None):
262 def __init__(self):
265 """Create a new InputSplitter instance.
263 """Create a new InputSplitter instance.
266
267 Parameters
268 ----------
269 input_mode : str
270
271 One of ['line', 'cell']; default is 'line'.
272
273 The input_mode parameter controls how new inputs are used when fed via
274 the :meth:`push` method:
275
276 - 'line': meant for line-oriented clients, inputs are appended one at a
277 time to the internal buffer and the whole buffer is compiled.
278
279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
280 a time. A cell can contain one or more blocks that can be compile in
281 'single' mode by Python. In this mode, each new input new input
282 completely replaces all prior inputs. Cell mode is thus equivalent
283 to prepending a full reset() to every push() call.
284 """
264 """
285 self._buffer = []
265 self._buffer = []
286 self._compile = codeop.CommandCompiler()
266 self._compile = codeop.CommandCompiler()
287 self.encoding = get_input_encoding()
267 self.encoding = get_input_encoding()
288 self.input_mode = InputSplitter.input_mode if input_mode is None \
289 else input_mode
290
268
291 def reset(self):
269 def reset(self):
292 """Reset the input buffer and associated state."""
270 """Reset the input buffer and associated state."""
@@ -326,9 +304,6 b' class InputSplitter(object):'
326 this value is also stored as a private attribute (``_is_complete``), so it
304 this value is also stored as a private attribute (``_is_complete``), so it
327 can be queried at any time.
305 can be queried at any time.
328 """
306 """
329 if self.input_mode == 'cell':
330 self.reset()
331
332 self._store(lines)
307 self._store(lines)
333 source = self.source
308 source = self.source
334
309
@@ -388,39 +363,34 b' class InputSplitter(object):'
388 """
363 """
389
364
390 # With incomplete input, unconditionally accept more
365 # With incomplete input, unconditionally accept more
366 # A syntax error also sets _is_complete to True - see push()
391 if not self._is_complete:
367 if not self._is_complete:
368 #print("Not complete") # debug
392 return True
369 return True
393
370
394 # If we already have complete input and we're flush left, the answer
371 # The user can make any (complete) input execute by leaving a blank line
395 # depends. In line mode, if there hasn't been any indentation,
372 last_line = self.source.splitlines()[-1]
396 # that's it. If we've come back from some indentation, we need
373 if (not last_line) or last_line.isspace():
397 # the blank final line to finish.
374 #print("Blank line") # debug
398 # In cell mode, we need to check how many blocks the input so far
399 # compiles into, because if there's already more than one full
400 # independent block of input, then the client has entered full
401 # 'cell' mode and is feeding lines that each is complete. In this
402 # case we should then keep accepting. The Qt terminal-like console
403 # does precisely this, to provide the convenience of terminal-like
404 # input of single expressions, but allowing the user (with a
405 # separate keystroke) to switch to 'cell' mode and type multiple
406 # expressions in one shot.
407 if self.indent_spaces==0:
408 if self.input_mode=='line':
409 if not self._full_dedent:
410 return False
375 return False
411 else:
376
377 # If there's just a single AST node, and we're flush left, as is the
378 # case after a simple statement such as 'a=1', we want to execute it
379 # straight away.
380 if self.indent_spaces==0:
412 try:
381 try:
413 code_ast = ast.parse(u''.join(self._buffer))
382 code_ast = ast.parse(u''.join(self._buffer))
414 except Exception:
383 except Exception:
384 #print("Can't parse AST") # debug
415 return False
385 return False
416 else:
386 else:
417 if len(code_ast.body) == 1:
387 if len(code_ast.body) == 1 and \
388 not hasattr(code_ast.body[0], 'body'):
389 #print("Simple statement") # debug
418 return False
390 return False
419
391
420 # When input is complete, then termination is marked by an extra blank
392 # General fallback - accept more code
421 # line at the end.
393 return True
422 last_line = self.source.splitlines()[-1]
423 return bool(last_line and not last_line.isspace())
424
394
425 #------------------------------------------------------------------------
395 #------------------------------------------------------------------------
426 # Private interface
396 # Private interface
@@ -510,9 +480,9 b' class IPythonInputSplitter(InputSplitter):'
510 # List with lines of raw input accumulated so far.
480 # List with lines of raw input accumulated so far.
511 _buffer_raw = None
481 _buffer_raw = None
512
482
513 def __init__(self, input_mode=None, physical_line_transforms=None,
483 def __init__(self, physical_line_transforms=None,
514 logical_line_transforms=None, python_line_transforms=None):
484 logical_line_transforms=None, python_line_transforms=None):
515 super(IPythonInputSplitter, self).__init__(input_mode)
485 super(IPythonInputSplitter, self).__init__()
516 self._buffer_raw = []
486 self._buffer_raw = []
517 self._validate = True
487 self._validate = True
518
488
@@ -641,43 +611,13 b' class IPythonInputSplitter(InputSplitter):'
641 if not lines_list:
611 if not lines_list:
642 lines_list = ['']
612 lines_list = ['']
643
613
644 # Transform logic
645 #
646 # We only apply the line transformers to the input if we have either no
647 # input yet, or complete input, or if the last line of the buffer ends
648 # with ':' (opening an indented block). This prevents the accidental
649 # transformation of escapes inside multiline expressions like
650 # triple-quoted strings or parenthesized expressions.
651 #
652 # The last heuristic, while ugly, ensures that the first line of an
653 # indented block is correctly transformed.
654 #
655 # FIXME: try to find a cleaner approach for this last bit.
656
657 # If we were in 'block' mode, since we're going to pump the parent
658 # class by hand line by line, we need to temporarily switch out to
659 # 'line' mode, do a single manual reset and then feed the lines one
660 # by one. Note that this only matters if the input has more than one
661 # line.
662 changed_input_mode = False
663
664 if self.input_mode == 'cell':
665 self.reset()
666 changed_input_mode = True
667 saved_input_mode = 'cell'
668 self.input_mode = 'line'
669
670 # Store raw source before applying any transformations to it. Note
614 # Store raw source before applying any transformations to it. Note
671 # that this must be done *after* the reset() call that would otherwise
615 # that this must be done *after* the reset() call that would otherwise
672 # flush the buffer.
616 # flush the buffer.
673 self._store(lines, self._buffer_raw, 'source_raw')
617 self._store(lines, self._buffer_raw, 'source_raw')
674
618
675 try:
676 for line in lines_list:
619 for line in lines_list:
677 out = self.push_line(line)
620 out = self.push_line(line)
678 finally:
679 if changed_input_mode:
680 self.input_mode = saved_input_mode
681
621
682 return out
622 return out
683
623
@@ -168,9 +168,6 b' class InputSplitterTestCase(unittest.TestCase):'
168 self.assertEqual(isp.indent_spaces, 0)
168 self.assertEqual(isp.indent_spaces, 0)
169
169
170 def test_indent2(self):
170 def test_indent2(self):
171 # In cell mode, inputs must be fed in whole blocks, so skip this test
172 if self.isp.input_mode == 'cell': return
173
174 isp = self.isp
171 isp = self.isp
175 isp.push('if 1:')
172 isp.push('if 1:')
176 self.assertEqual(isp.indent_spaces, 4)
173 self.assertEqual(isp.indent_spaces, 4)
@@ -181,9 +178,6 b' class InputSplitterTestCase(unittest.TestCase):'
181 self.assertEqual(isp.indent_spaces, 4)
178 self.assertEqual(isp.indent_spaces, 4)
182
179
183 def test_indent3(self):
180 def test_indent3(self):
184 # In cell mode, inputs must be fed in whole blocks, so skip this test
185 if self.isp.input_mode == 'cell': return
186
187 isp = self.isp
181 isp = self.isp
188 # When a multiline statement contains parens or multiline strings, we
182 # When a multiline statement contains parens or multiline strings, we
189 # shouldn't get confused.
183 # shouldn't get confused.
@@ -192,9 +186,6 b' class InputSplitterTestCase(unittest.TestCase):'
192 self.assertEqual(isp.indent_spaces, 4)
186 self.assertEqual(isp.indent_spaces, 4)
193
187
194 def test_indent4(self):
188 def test_indent4(self):
195 # In cell mode, inputs must be fed in whole blocks, so skip this test
196 if self.isp.input_mode == 'cell': return
197
198 isp = self.isp
189 isp = self.isp
199 # whitespace after ':' should not screw up indent level
190 # whitespace after ':' should not screw up indent level
200 isp.push('if 1: \n x=1')
191 isp.push('if 1: \n x=1')
@@ -279,23 +270,12 b' class InputSplitterTestCase(unittest.TestCase):'
279 isp.push(' a = 1')
270 isp.push(' a = 1')
280 self.assertFalse(isp.push('b = [1,'))
271 self.assertFalse(isp.push('b = [1,'))
281
272
282 def test_replace_mode(self):
283 isp = self.isp
284 isp.input_mode = 'cell'
285 isp.push('x=1')
286 self.assertEqual(isp.source, 'x=1\n')
287 isp.push('x=2')
288 self.assertEqual(isp.source, 'x=2\n')
289
290 def test_push_accepts_more(self):
273 def test_push_accepts_more(self):
291 isp = self.isp
274 isp = self.isp
292 isp.push('x=1')
275 isp.push('x=1')
293 self.assertFalse(isp.push_accepts_more())
276 self.assertFalse(isp.push_accepts_more())
294
277
295 def test_push_accepts_more2(self):
278 def test_push_accepts_more2(self):
296 # In cell mode, inputs must be fed in whole blocks, so skip this test
297 if self.isp.input_mode == 'cell': return
298
299 isp = self.isp
279 isp = self.isp
300 isp.push('if 1:')
280 isp.push('if 1:')
301 self.assertTrue(isp.push_accepts_more())
281 self.assertTrue(isp.push_accepts_more())
@@ -310,9 +290,6 b' class InputSplitterTestCase(unittest.TestCase):'
310 self.assertFalse(isp.push_accepts_more())
290 self.assertFalse(isp.push_accepts_more())
311
291
312 def test_push_accepts_more4(self):
292 def test_push_accepts_more4(self):
313 # In cell mode, inputs must be fed in whole blocks, so skip this test
314 if self.isp.input_mode == 'cell': return
315
316 isp = self.isp
293 isp = self.isp
317 # When a multiline statement contains parens or multiline strings, we
294 # When a multiline statement contains parens or multiline strings, we
318 # shouldn't get confused.
295 # shouldn't get confused.
@@ -331,14 +308,13 b' class InputSplitterTestCase(unittest.TestCase):'
331 self.assertFalse(isp.push_accepts_more())
308 self.assertFalse(isp.push_accepts_more())
332
309
333 def test_push_accepts_more5(self):
310 def test_push_accepts_more5(self):
334 # In cell mode, inputs must be fed in whole blocks, so skip this test
335 if self.isp.input_mode == 'cell': return
336
337 isp = self.isp
311 isp = self.isp
338 isp.push('try:')
312 isp.push('try:')
339 isp.push(' a = 5')
313 isp.push(' a = 5')
340 isp.push('except:')
314 isp.push('except:')
341 isp.push(' raise')
315 isp.push(' raise')
316 # We want to be able to add an else: block at this point, so it should
317 # wait for a blank line.
342 self.assertTrue(isp.push_accepts_more())
318 self.assertTrue(isp.push_accepts_more())
343
319
344 def test_continuation(self):
320 def test_continuation(self):
@@ -431,7 +407,7 b' class IPythonInputTestCase(InputSplitterTestCase):'
431 """
407 """
432
408
433 def setUp(self):
409 def setUp(self):
434 self.isp = isp.IPythonInputSplitter(input_mode='line')
410 self.isp = isp.IPythonInputSplitter()
435
411
436 def test_syntax(self):
412 def test_syntax(self):
437 """Call all single-line syntax tests from the main object"""
413 """Call all single-line syntax tests from the main object"""
@@ -467,32 +443,6 b' class IPythonInputTestCase(InputSplitterTestCase):'
467 self.assertEqual(out.rstrip(), out_t)
443 self.assertEqual(out.rstrip(), out_t)
468 self.assertEqual(out_raw.rstrip(), raw)
444 self.assertEqual(out_raw.rstrip(), raw)
469
445
470
471 class BlockIPythonInputTestCase(IPythonInputTestCase):
472
473 # Deactivate tests that don't make sense for the block mode
474 test_push3 = test_split = lambda s: None
475
476 def setUp(self):
477 self.isp = isp.IPythonInputSplitter(input_mode='cell')
478
479 def test_syntax_multiline(self):
480 isp = self.isp
481 for example in syntax_ml.itervalues():
482 raw_parts = []
483 out_t_parts = []
484 for line_pairs in example:
485 raw_parts, out_t_parts = zip(*line_pairs)
486
487 raw = '\n'.join(r for r in raw_parts if r is not None)
488 out_t = '\n'.join(o for o in out_t_parts if o is not None)
489
490 isp.push(raw)
491 out, out_raw = isp.source_raw_reset()
492 # Match ignoring trailing whitespace
493 self.assertEqual(out.rstrip(), out_t.rstrip())
494 self.assertEqual(out_raw.rstrip(), raw.rstrip())
495
496 def test_syntax_multiline_cell(self):
446 def test_syntax_multiline_cell(self):
497 isp = self.isp
447 isp = self.isp
498 for example in syntax_ml.itervalues():
448 for example in syntax_ml.itervalues():
@@ -146,7 +146,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 self._hidden = False
147 self._hidden = False
148 self._highlighter = FrontendHighlighter(self)
148 self._highlighter = FrontendHighlighter(self)
149 self._input_splitter = self._input_splitter_class(input_mode='cell')
149 self._input_splitter = self._input_splitter_class()
150 self._kernel_manager = None
150 self._kernel_manager = None
151 self._request_info = {}
151 self._request_info = {}
152 self._request_info['execute'] = {};
152 self._request_info['execute'] = {};
@@ -204,6 +204,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
204 prompt created. When triggered by an Enter/Return key press,
204 prompt created. When triggered by an Enter/Return key press,
205 'interactive' is True; otherwise, it is False.
205 'interactive' is True; otherwise, it is False.
206 """
206 """
207 self._input_splitter.reset()
207 complete = self._input_splitter.push(source)
208 complete = self._input_splitter.push(source)
208 if interactive:
209 if interactive:
209 complete = not self._input_splitter.push_accepts_more()
210 complete = not self._input_splitter.push_accepts_more()
@@ -82,7 +82,7 b' def get_pasted_lines(sentinel, l_input=py3compat.input):'
82 class TerminalMagics(Magics):
82 class TerminalMagics(Magics):
83 def __init__(self, shell):
83 def __init__(self, shell):
84 super(TerminalMagics, self).__init__(shell)
84 super(TerminalMagics, self).__init__(shell)
85 self.input_splitter = IPythonInputSplitter(input_mode='line')
85 self.input_splitter = IPythonInputSplitter()
86
86
87 def cleanup_input(self, block):
87 def cleanup_input(self, block):
88 """Apply all possible IPython cleanups to an input block.
88 """Apply all possible IPython cleanups to an input block.
General Comments 0
You need to be logged in to leave comments. Login now