diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index ab1ed89..39f66be 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -71,7 +71,6 @@ import sys # IPython modules from IPython.utils.text import make_quoted_expr - #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -223,7 +222,7 @@ class InputSplitter(object): ---------- input_mode : str - One of 'append', 'replace', default is 'append'. This controls how + One of ['append', 'replace']; default is 'append'. This controls how new inputs are used: in 'append' mode, they are appended to the existing buffer and the whole buffer is compiled; in 'replace' mode, each new input completely replaces all prior inputs. Replace mode is @@ -680,8 +679,10 @@ def transform_ipy_prompt(line): if not line or line.isspace(): return line + #print 'LINE: %r' % line # dbg m = _ipy_prompt_re.match(line) if m: + #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg return line[len(m.group(0)):] else: return line @@ -828,12 +829,30 @@ class IPythonInputSplitter(InputSplitter): # # FIXME: try to find a cleaner approach for this last bit. - for line in lines_list: - if self._is_complete or not self._buffer or \ - (self._buffer and self._buffer[-1].rstrip().endswith(':')): - for f in transforms: - line = f(line) - - out = super(IPythonInputSplitter, self).push(line) + # If we were in 'replace' mode, since we're going to pump the parent + # class by hand line by line, we need to temporarily switch out to + # 'append' 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 len(lines_list)>1 and self.input_mode == 'replace': + self.reset() + changed_input_mode = True + saved_input_mode = 'replace' + self.input_mode = 'append' + try: + push = super(IPythonInputSplitter, self).push + for line in lines_list: + if self._is_complete or not self._buffer or \ + (self._buffer and self._buffer[-1].rstrip().endswith(':')): + for f in transforms: + line = f(line) + + out = push(line) + finally: + if changed_input_mode: + self.input_mode = saved_input_mode + return out diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 635d5fe..67605a5 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -411,13 +411,15 @@ syntax = \ classic_prompt = [('>>> x=1', 'x=1'), ('x=1', 'x=1'), # normal input is unmodified - (' ',' '), # blank lines are kept intact + (' ', ' '), # blank lines are kept intact + ('... ', ''), # continuation prompts ], ipy_prompt = [('In [1]: x=1', 'x=1'), ('x=1', 'x=1'), # normal input is unmodified (' ',' '), # blank lines are kept intact + (' ....: ', ''), # continuation prompts ], # Tests for the escape transformer to leave normal code alone @@ -474,9 +476,6 @@ syntax = \ ('/f a b', 'f(a, b)'), ], - # More complex multiline tests - ## escaped_multiline = - ## [()], ) # multiline syntax examples. Each of these should be a list of lists, with @@ -555,8 +554,9 @@ class IPythonInputTestCase(InputSplitterTestCase): In addition, this runs the tests over the syntax and syntax_ml dicts that were tested by individual functions, as part of the OO interface. """ + def setUp(self): - self.isp = isp.IPythonInputSplitter() + self.isp = isp.IPythonInputSplitter(input_mode='append') def test_syntax(self): """Call all single-line syntax tests from the main object""" @@ -569,7 +569,7 @@ class IPythonInputTestCase(InputSplitterTestCase): isp.push(raw) out = isp.source_reset().rstrip() self.assertEqual(out, out_t) - + def test_syntax_multiline(self): isp = self.isp for example in syntax_ml.itervalues(): @@ -583,15 +583,41 @@ class IPythonInputTestCase(InputSplitterTestCase): out_t = '\n'.join(out_t_parts).rstrip() self.assertEqual(out, out_t) - + +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='replace') + + def test_syntax_multiline(self): + isp = self.isp + for example in syntax_ml.itervalues(): + raw_parts = [] + out_t_parts = [] + for line_pairs in example: + for raw, out_t_part in line_pairs: + raw_parts.append(raw) + out_t_parts.append(out_t_part) + + raw = '\n'.join(raw_parts) + out_t = '\n'.join(out_t_parts) + + isp.push(raw) + out = isp.source_reset() + # Match ignoring trailing whitespace + self.assertEqual(out.rstrip(), out_t.rstrip()) + + #----------------------------------------------------------------------------- -# Main - use as a script +# Main - use as a script, mostly for developer experiments #----------------------------------------------------------------------------- if __name__ == '__main__': # A simple demo for interactive experimentation. This code will not get - # picked up by any test suite. Useful mostly for illustration and during - # development. + # picked up by any test suite. from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter # configure here the syntax to use, prompt and whether to autoindent diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index eec36d2..044e9b7 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -60,7 +60,7 @@ class IPythonWidget(FrontendWidget): out_prompt = 'Out[%i]: ' # FrontendWidget protected class variables. - #_input_splitter_class = IPythonInputSplitter + _input_splitter_class = IPythonInputSplitter # IPythonWidget protected class variables. _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'