From ef0d530d5112de214868efc34caada74c3a0e2fc 2013-04-19 21:19:15 From: Thomas Kluyver Date: 2013-04-19 21:19:15 Subject: [PATCH] Simplify InputSplitter by stripping out input_mode distinction --- diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index 5b316a9..bae1c74 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -247,8 +247,6 @@ class InputSplitter(object): # synced to the source, so it can be queried at any time to obtain the code # object; it will be None if the source doesn't compile to valid Python. code = None - # Input mode - input_mode = 'line' # Private attributes @@ -261,32 +259,12 @@ class InputSplitter(object): # Boolean indicating whether the current block is complete _is_complete = None - def __init__(self, input_mode=None): + def __init__(self): """Create a new InputSplitter instance. - - Parameters - ---------- - input_mode : str - - One of ['line', 'cell']; default is 'line'. - - The input_mode parameter controls how new inputs are used when fed via - the :meth:`push` method: - - - 'line': meant for line-oriented clients, inputs are appended one at a - time to the internal buffer and the whole buffer is compiled. - - - 'cell': meant for clients that can edit multi-line 'cells' of text at - a time. A cell can contain one or more blocks that can be compile in - 'single' mode by Python. In this mode, each new input new input - completely replaces all prior inputs. Cell mode is thus equivalent - to prepending a full reset() to every push() call. """ self._buffer = [] self._compile = codeop.CommandCompiler() self.encoding = get_input_encoding() - self.input_mode = InputSplitter.input_mode if input_mode is None \ - else input_mode def reset(self): """Reset the input buffer and associated state.""" @@ -326,9 +304,6 @@ class InputSplitter(object): this value is also stored as a private attribute (``_is_complete``), so it can be queried at any time. """ - if self.input_mode == 'cell': - self.reset() - self._store(lines) source = self.source @@ -388,39 +363,34 @@ class InputSplitter(object): """ # With incomplete input, unconditionally accept more + # A syntax error also sets _is_complete to True - see push() if not self._is_complete: + #print("Not complete") # debug return True - - # If we already have complete input and we're flush left, the answer - # depends. In line mode, if there hasn't been any indentation, - # that's it. If we've come back from some indentation, we need - # the blank final line to finish. - # In cell mode, we need to check how many blocks the input so far - # compiles into, because if there's already more than one full - # independent block of input, then the client has entered full - # 'cell' mode and is feeding lines that each is complete. In this - # case we should then keep accepting. The Qt terminal-like console - # does precisely this, to provide the convenience of terminal-like - # input of single expressions, but allowing the user (with a - # separate keystroke) to switch to 'cell' mode and type multiple - # expressions in one shot. + + # The user can make any (complete) input execute by leaving a blank line + last_line = self.source.splitlines()[-1] + if (not last_line) or last_line.isspace(): + #print("Blank line") # debug + return False + + # If there's just a single AST node, and we're flush left, as is the + # case after a simple statement such as 'a=1', we want to execute it + # straight away. if self.indent_spaces==0: - if self.input_mode=='line': - if not self._full_dedent: - return False + try: + code_ast = ast.parse(u''.join(self._buffer)) + except Exception: + #print("Can't parse AST") # debug + return False else: - try: - code_ast = ast.parse(u''.join(self._buffer)) - except Exception: + if len(code_ast.body) == 1 and \ + not hasattr(code_ast.body[0], 'body'): + #print("Simple statement") # debug return False - else: - if len(code_ast.body) == 1: - return False - # When input is complete, then termination is marked by an extra blank - # line at the end. - last_line = self.source.splitlines()[-1] - return bool(last_line and not last_line.isspace()) + # General fallback - accept more code + return True #------------------------------------------------------------------------ # Private interface @@ -510,9 +480,9 @@ class IPythonInputSplitter(InputSplitter): # List with lines of raw input accumulated so far. _buffer_raw = None - def __init__(self, input_mode=None, physical_line_transforms=None, + def __init__(self, physical_line_transforms=None, logical_line_transforms=None, python_line_transforms=None): - super(IPythonInputSplitter, self).__init__(input_mode) + super(IPythonInputSplitter, self).__init__() self._buffer_raw = [] self._validate = True @@ -641,44 +611,14 @@ class IPythonInputSplitter(InputSplitter): if not lines_list: lines_list = [''] - # Transform logic - # - # We only apply the line transformers to the input if we have either no - # input yet, or complete input, or if the last line of the buffer ends - # with ':' (opening an indented block). This prevents the accidental - # transformation of escapes inside multiline expressions like - # triple-quoted strings or parenthesized expressions. - # - # The last heuristic, while ugly, ensures that the first line of an - # indented block is correctly transformed. - # - # FIXME: try to find a cleaner approach for this last bit. - - # If we were in 'block' mode, since we're going to pump the parent - # class by hand line by line, we need to temporarily switch out to - # 'line' mode, do a single manual reset and then feed the lines one - # by one. Note that this only matters if the input has more than one - # line. - changed_input_mode = False - - if self.input_mode == 'cell': - self.reset() - changed_input_mode = True - saved_input_mode = 'cell' - self.input_mode = 'line' - # Store raw source before applying any transformations to it. Note # that this must be done *after* the reset() call that would otherwise # flush the buffer. self._store(lines, self._buffer_raw, 'source_raw') - try: - for line in lines_list: - out = self.push_line(line) - finally: - if changed_input_mode: - self.input_mode = saved_input_mode - + for line in lines_list: + out = self.push_line(line) + return out def push_line(self, line): diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index a351bb0..bdaab48 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -168,9 +168,6 @@ class InputSplitterTestCase(unittest.TestCase): self.assertEqual(isp.indent_spaces, 0) def test_indent2(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp isp.push('if 1:') self.assertEqual(isp.indent_spaces, 4) @@ -181,9 +178,6 @@ class InputSplitterTestCase(unittest.TestCase): self.assertEqual(isp.indent_spaces, 4) def test_indent3(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp # When a multiline statement contains parens or multiline strings, we # shouldn't get confused. @@ -192,9 +186,6 @@ class InputSplitterTestCase(unittest.TestCase): self.assertEqual(isp.indent_spaces, 4) def test_indent4(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp # whitespace after ':' should not screw up indent level isp.push('if 1: \n x=1') @@ -279,23 +270,12 @@ class InputSplitterTestCase(unittest.TestCase): isp.push(' a = 1') self.assertFalse(isp.push('b = [1,')) - def test_replace_mode(self): - isp = self.isp - isp.input_mode = 'cell' - isp.push('x=1') - self.assertEqual(isp.source, 'x=1\n') - isp.push('x=2') - self.assertEqual(isp.source, 'x=2\n') - def test_push_accepts_more(self): isp = self.isp isp.push('x=1') self.assertFalse(isp.push_accepts_more()) def test_push_accepts_more2(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp isp.push('if 1:') self.assertTrue(isp.push_accepts_more()) @@ -310,9 +290,6 @@ class InputSplitterTestCase(unittest.TestCase): self.assertFalse(isp.push_accepts_more()) def test_push_accepts_more4(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp # When a multiline statement contains parens or multiline strings, we # shouldn't get confused. @@ -331,14 +308,13 @@ class InputSplitterTestCase(unittest.TestCase): self.assertFalse(isp.push_accepts_more()) def test_push_accepts_more5(self): - # In cell mode, inputs must be fed in whole blocks, so skip this test - if self.isp.input_mode == 'cell': return - isp = self.isp isp.push('try:') isp.push(' a = 5') isp.push('except:') isp.push(' raise') + # We want to be able to add an else: block at this point, so it should + # wait for a blank line. self.assertTrue(isp.push_accepts_more()) def test_continuation(self): @@ -431,7 +407,7 @@ class IPythonInputTestCase(InputSplitterTestCase): """ def setUp(self): - self.isp = isp.IPythonInputSplitter(input_mode='line') + self.isp = isp.IPythonInputSplitter() def test_syntax(self): """Call all single-line syntax tests from the main object""" @@ -467,32 +443,6 @@ class IPythonInputTestCase(InputSplitterTestCase): self.assertEqual(out.rstrip(), out_t) self.assertEqual(out_raw.rstrip(), raw) - -class BlockIPythonInputTestCase(IPythonInputTestCase): - - # Deactivate tests that don't make sense for the block mode - test_push3 = test_split = lambda s: None - - def setUp(self): - self.isp = isp.IPythonInputSplitter(input_mode='cell') - - def test_syntax_multiline(self): - isp = self.isp - for example in syntax_ml.itervalues(): - raw_parts = [] - out_t_parts = [] - for line_pairs in example: - raw_parts, out_t_parts = zip(*line_pairs) - - raw = '\n'.join(r for r in raw_parts if r is not None) - out_t = '\n'.join(o for o in out_t_parts if o is not None) - - isp.push(raw) - out, out_raw = isp.source_raw_reset() - # Match ignoring trailing whitespace - self.assertEqual(out.rstrip(), out_t.rstrip()) - self.assertEqual(out_raw.rstrip(), raw.rstrip()) - def test_syntax_multiline_cell(self): isp = self.isp for example in syntax_ml.itervalues(): diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 3fe0203..2fe1cfa 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -146,7 +146,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self) - self._input_splitter = self._input_splitter_class(input_mode='cell') + self._input_splitter = self._input_splitter_class() self._kernel_manager = None self._request_info = {} self._request_info['execute'] = {}; @@ -204,6 +204,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): prompt created. When triggered by an Enter/Return key press, 'interactive' is True; otherwise, it is False. """ + self._input_splitter.reset() complete = self._input_splitter.push(source) if interactive: complete = not self._input_splitter.push_accepts_more() diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py index 030b46a..19ce96b 100644 --- a/IPython/frontend/terminal/interactiveshell.py +++ b/IPython/frontend/terminal/interactiveshell.py @@ -82,7 +82,7 @@ def get_pasted_lines(sentinel, l_input=py3compat.input): class TerminalMagics(Magics): def __init__(self, shell): super(TerminalMagics, self).__init__(shell) - self.input_splitter = IPythonInputSplitter(input_mode='line') + self.input_splitter = IPythonInputSplitter() def cleanup_input(self, block): """Apply all possible IPython cleanups to an input block.