Repository: ipython/ipython

Issue:
Issues with Ellipsis literal in Python 3
There seem to be some differences between the way the Ellipsis literal (`...`) from Python 3 is handled in python vs. ipython:

``` py
>>> ...
Ellipsis
>>> ....__class__
<class 'ellipsis'>
```

``` py
In [1]: ...

In [2]: ....__class__
  File "<ipython-input-2-f022736bb488>", line 1
    .__class__
    ^
SyntaxError: invalid syntax
```



Discussion:
My guess is it's the prompt-stripping input transformer being overzealous, since `...` is a continuation prompt.

The `...` is being cleared or something. 

``` py
>>> ... + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'ellipsis' and 'int'
```

``` py
In [10]: ... + 1
Out[10]: 1
```

Ahh, that is probably it. Is it possible to be unambiguous here?

I guess not, because for example

``` py
>>> def test():
...     print(1)
...     +1
```

could mean `+1` is a statement in `test`, or it could mean that the function definition is over and you want to add `...` and `1`, and without %paste, there's no way to know if a statement is part of what was just pasted in or if it was just typed out (right?).  

Also, as a simpler example, a bare `...` is allowed after any `>>>`. 

On the other hand, certain uses of `...` at the beginning of a line are not allowed (in a doctest or Python prompt), like not following a `>>>`, or following a `>>>` that is a complete statement.

I don't know what the right things to do here is. All I can say is, "in the face of ambiguity, refuse the temptation to guess."

Right now, we strip any prompt from any line.  The only way that I see to do better, given the ambiguity of leading `...`, is to only strip continuation prompts if the initial prompt is present.

That way, you can copy/paste

``` python
>>> def test():
...    print(1)
...    return 1
```

But you will _not_ be able to paste an excerpted block, starting after the first line.  I think this is a reasonable compromise, but others may disagree.

I can't think of a better way. It leads to a nasty gotcha, but at least the common case of pasting stuff and/or playing around with ellipsis literals will work (as long as you don't do one immediately after the other). 


Resolving patch:
diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py
--- a/IPython/core/inputtransformer.py
+++ b/IPython/core/inputtransformer.py
@@ -359,8 +359,26 @@ def cellmagic(end_on_blank_line=False):
         line = tpl % (magic_name, first, u'\n'.join(body))
 
 
-def _strip_prompts(prompt_re):
-    """Remove matching input prompts from a block of input."""
+def _strip_prompts(prompt_re, initial_re=None):
+    """Remove matching input prompts from a block of input.
+    
+    Parameters
+    ----------
+    prompt_re : regular expression
+        A regular expression matching any input prompt (including continuation)
+    initial_re : regular expression, optional
+        A regular expression matching only the initial prompt, but not continuation.
+        If no initial expression is given, prompt_re will be used everywhere.
+        Used mainly for plain Python prompts, where the continuation prompt
+        ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
+    
+    If initial_re and prompt_re differ,
+    only initial_re will be tested against the first line.
+    If any prompt is found on the first two lines,
+    prompts will be stripped from the rest of the block.
+    """
+    if initial_re is None:
+        initial_re = prompt_re
     line = ''
     while True:
         line = (yield line)
@@ -368,18 +386,19 @@ def _strip_prompts(prompt_re):
         # First line of cell
         if line is None:
             continue
-        out, n1 = prompt_re.subn('', line, count=1)
+        out, n1 = initial_re.subn('', line, count=1)
         line = (yield out)
         
-        # Second line of cell, because people often copy from just after the
-        # first prompt, so we might not see it in the first line.
         if line is None:
             continue
+        # check for any prompt on the second line of the cell,
+        # because people often copy from just after the first prompt,
+        # so we might not see it in the first line.
         out, n2 = prompt_re.subn('', line, count=1)
         line = (yield out)
         
         if n1 or n2:
-            # Found the input prompt in the first two lines - check for it in
+            # Found a prompt in the first two lines - check for it in
             # the rest of the cell as well.
             while line is not None:
                 line = (yield prompt_re.sub('', line, count=1))
@@ -394,7 +413,8 @@ def classic_prompt():
     """Strip the >>>/... prompts of the Python interactive shell."""
     # FIXME: non-capturing version (?:...) usable?
     prompt_re = re.compile(r'^(>>> ?|\.\.\. ?)')
-    return _strip_prompts(prompt_re)
+    initial_re = re.compile(r'^(>>> ?)')
+    return _strip_prompts(prompt_re, initial_re)
 
 @CoroutineInputTransformer.wrap
 def ipy_prompt():


Test patch:
diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py
--- a/IPython/core/tests/test_inputtransformer.py
+++ b/IPython/core/tests/test_inputtransformer.py
@@ -182,10 +182,19 @@ def transform_checker(tests, transformer, **kwargs):
           ('... 123','123'),
           ('... 456"""','456"""'),
           ],
+         [('a="""','a="""'),
+          ('>>> 123','123'),
+          ('... 456"""','456"""'),
+          ],
          [('a="""','a="""'),
           ('123','123'),
           ('... 456"""','... 456"""'),
           ],
+         [('....__class__','....__class__'),
+         ],
+         [('a=5', 'a=5'),
+          ('...', ''),
+         ],
          [('>>> def f(x):', 'def f(x):'),
           ('...', ''),
           ('...     return x', '    return x'),
@@ -204,6 +213,10 @@ def transform_checker(tests, transformer, **kwargs):
           ('   ...: 123','123'),
           ('   ...: 456"""','456"""'),
           ],
+         [('a="""','a="""'),
+          ('In [1]: 123','123'),
+          ('   ...: 456"""','456"""'),
+          ],
          [('a="""','a="""'),
           ('123','123'),
           ('   ...: 456"""','   ...: 456"""'),