From 9bd0c900936828c84817865727be224b5c44d13d 2012-07-08 04:22:47 From: Aaron Meurer 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