##// 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 247 # synced to the source, so it can be queried at any time to obtain the code
248 248 # object; it will be None if the source doesn't compile to valid Python.
249 249 code = None
250 # Input mode
251 input_mode = 'line'
252 250
253 251 # Private attributes
254 252
@@ -261,32 +259,12 b' class InputSplitter(object):'
261 259 # Boolean indicating whether the current block is complete
262 260 _is_complete = None
263 261
264 def __init__(self, input_mode=None):
262 def __init__(self):
265 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 265 self._buffer = []
286 266 self._compile = codeop.CommandCompiler()
287 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 269 def reset(self):
292 270 """Reset the input buffer and associated state."""
@@ -326,9 +304,6 b' class InputSplitter(object):'
326 304 this value is also stored as a private attribute (``_is_complete``), so it
327 305 can be queried at any time.
328 306 """
329 if self.input_mode == 'cell':
330 self.reset()
331
332 307 self._store(lines)
333 308 source = self.source
334 309
@@ -388,39 +363,34 b' class InputSplitter(object):'
388 363 """
389 364
390 365 # With incomplete input, unconditionally accept more
366 # A syntax error also sets _is_complete to True - see push()
391 367 if not self._is_complete:
368 #print("Not complete") # debug
392 369 return True
393
394 # If we already have complete input and we're flush left, the answer
395 # depends. In line mode, if there hasn't been any indentation,
396 # that's it. If we've come back from some indentation, we need
397 # the blank final line to finish.
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.
370
371 # The user can make any (complete) input execute by leaving a blank line
372 last_line = self.source.splitlines()[-1]
373 if (not last_line) or last_line.isspace():
374 #print("Blank line") # debug
375 return False
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.
407 380 if self.indent_spaces==0:
408 if self.input_mode=='line':
409 if not self._full_dedent:
410 return False
381 try:
382 code_ast = ast.parse(u''.join(self._buffer))
383 except Exception:
384 #print("Can't parse AST") # debug
385 return False
411 386 else:
412 try:
413 code_ast = ast.parse(u''.join(self._buffer))
414 except Exception:
387 if len(code_ast.body) == 1 and \
388 not hasattr(code_ast.body[0], 'body'):
389 #print("Simple statement") # debug
415 390 return False
416 else:
417 if len(code_ast.body) == 1:
418 return False
419 391
420 # When input is complete, then termination is marked by an extra blank
421 # line at the end.
422 last_line = self.source.splitlines()[-1]
423 return bool(last_line and not last_line.isspace())
392 # General fallback - accept more code
393 return True
424 394
425 395 #------------------------------------------------------------------------
426 396 # Private interface
@@ -510,9 +480,9 b' class IPythonInputSplitter(InputSplitter):'
510 480 # List with lines of raw input accumulated so far.
511 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 484 logical_line_transforms=None, python_line_transforms=None):
515 super(IPythonInputSplitter, self).__init__(input_mode)
485 super(IPythonInputSplitter, self).__init__()
516 486 self._buffer_raw = []
517 487 self._validate = True
518 488
@@ -641,44 +611,14 b' class IPythonInputSplitter(InputSplitter):'
641 611 if not lines_list:
642 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 614 # Store raw source before applying any transformations to it. Note
671 615 # that this must be done *after* the reset() call that would otherwise
672 616 # flush the buffer.
673 617 self._store(lines, self._buffer_raw, 'source_raw')
674 618
675 try:
676 for line in lines_list:
677 out = self.push_line(line)
678 finally:
679 if changed_input_mode:
680 self.input_mode = saved_input_mode
681
619 for line in lines_list:
620 out = self.push_line(line)
621
682 622 return out
683 623
684 624 def push_line(self, line):
@@ -168,9 +168,6 b' class InputSplitterTestCase(unittest.TestCase):'
168 168 self.assertEqual(isp.indent_spaces, 0)
169 169
170 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 171 isp = self.isp
175 172 isp.push('if 1:')
176 173 self.assertEqual(isp.indent_spaces, 4)
@@ -181,9 +178,6 b' class InputSplitterTestCase(unittest.TestCase):'
181 178 self.assertEqual(isp.indent_spaces, 4)
182 179
183 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 181 isp = self.isp
188 182 # When a multiline statement contains parens or multiline strings, we
189 183 # shouldn't get confused.
@@ -192,9 +186,6 b' class InputSplitterTestCase(unittest.TestCase):'
192 186 self.assertEqual(isp.indent_spaces, 4)
193 187
194 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 189 isp = self.isp
199 190 # whitespace after ':' should not screw up indent level
200 191 isp.push('if 1: \n x=1')
@@ -279,23 +270,12 b' class InputSplitterTestCase(unittest.TestCase):'
279 270 isp.push(' a = 1')
280 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 273 def test_push_accepts_more(self):
291 274 isp = self.isp
292 275 isp.push('x=1')
293 276 self.assertFalse(isp.push_accepts_more())
294 277
295 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 279 isp = self.isp
300 280 isp.push('if 1:')
301 281 self.assertTrue(isp.push_accepts_more())
@@ -310,9 +290,6 b' class InputSplitterTestCase(unittest.TestCase):'
310 290 self.assertFalse(isp.push_accepts_more())
311 291
312 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 293 isp = self.isp
317 294 # When a multiline statement contains parens or multiline strings, we
318 295 # shouldn't get confused.
@@ -331,14 +308,13 b' class InputSplitterTestCase(unittest.TestCase):'
331 308 self.assertFalse(isp.push_accepts_more())
332 309
333 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 311 isp = self.isp
338 312 isp.push('try:')
339 313 isp.push(' a = 5')
340 314 isp.push('except:')
341 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 318 self.assertTrue(isp.push_accepts_more())
343 319
344 320 def test_continuation(self):
@@ -431,7 +407,7 b' class IPythonInputTestCase(InputSplitterTestCase):'
431 407 """
432 408
433 409 def setUp(self):
434 self.isp = isp.IPythonInputSplitter(input_mode='line')
410 self.isp = isp.IPythonInputSplitter()
435 411
436 412 def test_syntax(self):
437 413 """Call all single-line syntax tests from the main object"""
@@ -467,32 +443,6 b' class IPythonInputTestCase(InputSplitterTestCase):'
467 443 self.assertEqual(out.rstrip(), out_t)
468 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 446 def test_syntax_multiline_cell(self):
497 447 isp = self.isp
498 448 for example in syntax_ml.itervalues():
@@ -146,7 +146,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
146 146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 147 self._hidden = False
148 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 150 self._kernel_manager = None
151 151 self._request_info = {}
152 152 self._request_info['execute'] = {};
@@ -204,6 +204,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
204 204 prompt created. When triggered by an Enter/Return key press,
205 205 'interactive' is True; otherwise, it is False.
206 206 """
207 self._input_splitter.reset()
207 208 complete = self._input_splitter.push(source)
208 209 if interactive:
209 210 complete = not self._input_splitter.push_accepts_more()
@@ -82,7 +82,7 b' def get_pasted_lines(sentinel, l_input=py3compat.input):'
82 82 class TerminalMagics(Magics):
83 83 def __init__(self, shell):
84 84 super(TerminalMagics, self).__init__(shell)
85 self.input_splitter = IPythonInputSplitter(input_mode='line')
85 self.input_splitter = IPythonInputSplitter()
86 86
87 87 def cleanup_input(self, block):
88 88 """Apply all possible IPython cleanups to an input block.
General Comments 0
You need to be logged in to leave comments. Login now