From 9bd0c900936828c84817865727be224b5c44d13d 2012-07-08 04:22:47
From: Aaron Meurer <asmeurer@gmail.com>
Date: 2012-07-08 04:22:47
Subject: [PATCH] Line continuations now terminate after one blank line (#2108)

Previously, we had

In [1]: 1\
...:
...:
...:
...:

In other words, no amount of blank lines would terminate after a line
continuation, in contrast with regular Python:

>>> 1\
...
1

This made things really annoying when I typed \ instead of a newline--quite
easy to do since they are right next to each other on the keyboard.

Now, we have

In [1]: 1\
...:
Out[1]: 1

This also fixes another related behavioral difference between IPython.  If a
space follows a line continuation character, it should be a
SyntaxError("unexpected character after line continuation character"), even if
the line is otherwise continuable, according to regular Python (e.g., `1 \ `
or `(1 + \ `).  This now consistent between the two.

Closes #2108

---

diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py
index f00cff0..18ac736 100644
--- a/IPython/core/inputsplitter.py
+++ b/IPython/core/inputsplitter.py
@@ -202,17 +202,17 @@ def remove_comments(src):
     """
 
     return re.sub('#.*', '', src)
-    
+
 def has_comment(src):
     """Indicate whether an input line has (i.e. ends in, or is) a comment.
-    
+
     This uses tokenize, so it can distinguish comments from # inside strings.
-    
+
     Parameters
     ----------
     src : string
       A single line input string.
-    
+
     Returns
     -------
     Boolean: True if source has a comment.
@@ -280,9 +280,9 @@ class InputSplitter(object):
     code = None
     # Input mode
     input_mode = 'line'
-    
+
     # Private attributes
-    
+
     # List with lines of input accumulated so far
     _buffer = None
     # Command compiler
@@ -291,7 +291,7 @@ class InputSplitter(object):
     _full_dedent = False
     # Boolean indicating whether the current block is complete
     _is_complete = None
-    
+
     def __init__(self, input_mode=None):
         """Create a new InputSplitter instance.
 
@@ -348,7 +348,7 @@ class InputSplitter(object):
         ----------
         lines : string
           One or more lines of Python input.
-        
+
         Returns
         -------
         is_complete : boolean
@@ -359,7 +359,7 @@ class InputSplitter(object):
         """
         if self.input_mode == 'cell':
             self.reset()
-        
+
         self._store(lines)
         source = self.source
 
@@ -369,7 +369,7 @@ class InputSplitter(object):
         self.code, self._is_complete = None, None
 
         # Honor termination lines properly
-        if source.rstrip().endswith('\\'):
+        if source.endswith('\\\n'):
             return False
 
         self._update_indent(lines)
@@ -400,11 +400,11 @@ class InputSplitter(object):
         SyntaxError is raised, or *all* of the following are true:
 
         1. The input compiles to a complete statement.
-        
+
         2. The indentation level is flush-left (because if we are indented,
            like inside a function definition or for loop, we need to keep
            reading new input).
-          
+
         3. There is one extra line consisting only of whitespace.
 
         Because of condition #3, this method should be used only by
@@ -464,7 +464,7 @@ class InputSplitter(object):
         ----------
         line : str
           A single new line of non-whitespace, non-comment Python input.
-          
+
         Returns
         -------
         indent_spaces : int
@@ -476,7 +476,7 @@ class InputSplitter(object):
         """
         indent_spaces = self.indent_spaces
         full_dedent = self._full_dedent
-        
+
         inisp = num_ini_spaces(line)
         if inisp < indent_spaces:
             indent_spaces = inisp
@@ -495,9 +495,9 @@ class InputSplitter(object):
         if indent_spaces < 0:
             indent_spaces = 0
             #print 'safety' # dbg
-            
+
         return indent_spaces, full_dedent
-    
+
     def _update_indent(self, lines):
         for line in remove_comments(lines).splitlines():
             if line and not line.isspace():
@@ -508,10 +508,10 @@ class InputSplitter(object):
 
         If input lines are not newline-terminated, a newline is automatically
         appended."""
-        
+
         if buffer is None:
             buffer = self._buffer
-            
+
         if lines.endswith('\n'):
             buffer.append(lines)
         else:
@@ -627,10 +627,10 @@ def transform_help_end(line):
     target = m.group(1)
     esc = m.group(3)
     lspace = _initial_space_re.match(line).group(0)
-    
+
     # If we're mid-command, put it back on the next prompt for the user.
     next_input = line.rstrip('?') if line.strip() != m.group(0) else None
-        
+
     return _make_help_call(target, esc, lspace, next_input)
 
 
@@ -647,7 +647,7 @@ class EscapedTransformer(object):
                ESC_QUOTE2 : self._tr_quote2,
                ESC_PAREN  : self._tr_paren }
         self.tr = tr
-        
+
     # Support for syntax transformations that use explicit escapes typed by the
     # user at the beginning of a line
     @staticmethod
@@ -668,7 +668,7 @@ class EscapedTransformer(object):
         # A naked help line should just fire the intro help screen
         if not line_info.line[1:]:
             return 'get_ipython().show_usage()'
-        
+
         return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
 
     @staticmethod
@@ -745,7 +745,7 @@ class IPythonInputSplitter(InputSplitter):
         super(IPythonInputSplitter, self).__init__(input_mode)
         self._buffer_raw = []
         self._validate = True
-        
+
     def reset(self):
         """Reset the input buffer and associated state."""
         super(IPythonInputSplitter, self).reset()
@@ -897,7 +897,7 @@ class IPythonInputSplitter(InputSplitter):
         # that this must be done *after* the reset() call that would otherwise
         # flush the buffer.
         self._store(lines, self._buffer_raw, 'source_raw')
-        
+
         try:
             push = super(IPythonInputSplitter, self).push
             buf = self._buffer
diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py
index 08e0848..865855a 100644
--- a/IPython/core/tests/test_inputsplitter.py
+++ b/IPython/core/tests/test_inputsplitter.py
@@ -351,6 +351,22 @@ class InputSplitterTestCase(unittest.TestCase):
         self.isp.push(u'\xc3\xa9')
         self.isp.push(u"u'\xc3\xa9'")
 
+    def test_line_continuation(self):
+        """ Test issue #2108."""
+        isp = self.isp
+        # A blank line after a line continuation should not accept more
+        isp.push("1 \\\n\n")
+        self.assertFalse(isp.push_accepts_more())
+        # Whitespace after a \ is a SyntaxError.  The only way to test that
+        # here is to test that push doesn't accept more (as with
+        # test_syntax_error() above).
+        isp.push(r"1 \ ")
+        self.assertFalse(isp.push_accepts_more())
+        # Even if the line is continuable (c.f. the regular Python
+        # interpreter)
+        isp.push(r"(1 \ ")
+        self.assertFalse(isp.push_accepts_more())
+
 class InteractiveLoopTestCase(unittest.TestCase):
     """Tests for an interactive loop like a python shell.
     """
@@ -678,7 +694,7 @@ class BlockIPythonInputTestCase(IPythonInputTestCase):
     def test_syntax_multiline_cell(self):
         isp = self.isp
         for example in syntax_ml.itervalues():
-            
+
             out_t_parts = []
             for line_pairs in example:
                 raw = '\n'.join(r for r, _ in line_pairs)
@@ -762,7 +778,7 @@ def test_last_two_blanks():
 
 
 class CellMagicsCommon(object):
-    
+
     def test_whole_cell(self):
         src = "%%cellm line\nbody\n"
         sp = self.sp