Repository: ipython/ipython

Issue:
Cannot make multi-line code blocks in ipython
I just did a `pip install ipython` while teaching a class today and typed an if statement and then hit enter after the first line and the code executed.

This seems like a bug.

I did a `pip install ipython==6.5.0` and hitting Enter in a code block properly showed me the next indented line to type out.

I'm on Ubuntu 18.04 running ipython within tmux, though I doubt tmux is the issue here.


Discussion:
you can use `ctrl-o` to force a new line. 
I never would have guessed that one. I'm not sure how to make this more clear, but I do think it warrants some clarification/hand-holding. I tried every shortcut I could think of and ended up forcing a downgrade to fix my issue.

I use IPython to make it easier to type out and edit multi-line blocks in the REPL while teaching live audiences. I suspect others in my situation may have similar issues.

Maybe a warning somewhere that indicates that Ctrl-O can be used to make a multi-line code block? I'm not sure whether there's an appropriate place for that.
It is a bug with a recent refactor, I was just trying telling you that you can use Ctrl-O in the meantime if you wish to use 7.x
Ah great! I'm glad it was just a bug. Thanks @Carreau! 😄
Would you mind giving me a few examples (working and not workig) of what you expect, in order to provide test for the fixes ? 

I have some but just collecting a few and don't want to influence you. 
```
with open('hello.txt', mode='wt') as my_file:
    my_file.write('hi')
    my_file.write('hi again')
```

Can't remember the second example, but a number of loops:

```
numbers = [2, 1, 3, 4, 7, 8, 11]

for n in numbers:
    if n > 0:
        print(n*2)
    else:
        print(n/2)
```
@Carreau is someone working on this issue? I'd be happy to help out
hi @Deborah-Digges, I've had a quick look but no too much so far. 

I've wrote this small test case to see the difference between the old input_splitter and the new input_transformer: 

```
from IPython.core import inputtransformer2 as ipt2 # new way
from IPython.core import inputsplitter #oldl way

occ =  inputsplitter.InputSplitter().check_complete
cc = ipt2.TransformerManager().check_complete

comp = lambda x : (cc(x), occ(x), x)
print(comp('if'))
print(comp('if\n\n'))
print(comp("""
def foo():
    print('Hello')"""
))
print(comp('if True:'))
```

Which result in 
```
(('invalid', None), ('invalid', None), 'if')
(('complete', None), ('invalid', None), 'if\n\n')
(('complete', None), ('incomplete', 4), "\ndef foo():\n    print('Hello')")
(('incomplete', 4), ('incomplete', 4), 'if True:')
```

You see that the 3rd item is the one that interest us. The second was out of curiosity as one of the "feature" of IPython is to force-execute if there is  more than 2 newlines. 

Part of the relevant code is in [shortcut.py:L109-L127](https://github.com/ipython/ipython/blob/194d1d7fe2fcc8547205bd7f54fcf63bee9df4e3/IPython/terminal/shortcuts.py#L109-L127). 

I guess there is some kind of conflation between the role of defining whether a check of code is "complete", or whether we should "execute or add a new line".

I'm _guessing_ that the heuristic that check whether the input is multiline and whether the last character is already a new line or not should be sufficient. 

One of the remaining questions is where to put that fix ? 
 - in `shortcut.py`? if so it will only fix terminal IPython
 - in input_transformer2 ? Ithink that might be the right solution as this but will likely also affect QtConsole. 

Let me know if that's enough to at least get you started. 

Thanks !
Hi @Carreau ! Thanks so much for the detailed explanation and test cases.

I was able to reproduce it locally with the dev version of ipython and will now start looking into the code for `shortcut.py` and `inputtransformer`.
> Would you mind giving me a few examples (working and not workig) of what you expect, in order to provide test for the fixes ?

You don't need a few examples. The bug occurs in very reproducible way.
Multiline works **only** if the line ends with colon. If there is no colon at the end of line then iPython executes code. This looks a bit more trivial with this information but never looked into the iPython source code, so maybe I am just a bit too optimistic here ;)
> iPython

Upper case I please, we don't want trouble  with apple. 

> You don't need a few examples

Well, no I don't _need_, but I want multiple example. I can reproduce and have an idea of how to fix it, but having multiple case help me be sure that I'm no hitting an edge case. I do have a biased view on how to use IPython, so example from other are helpful. 



Resolving patch:
diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -571,8 +571,7 @@ def check_complete(self, cell: str):
           The number of spaces by which to indent the next line of code. If
           status is not 'incomplete', this is None.
         """
-        if not cell.endswith('\n'):
-            cell += '\n'  # Ensure the cell has a trailing newline
+        cell += '\n'  # Ensure the cell has a trailing newline
         lines = cell.splitlines(keepends=True)
         if lines[-1][:-1].endswith('\\'):
             # Explicit backslash continuation
@@ -631,12 +630,13 @@ def check_complete(self, cell: str):
         try:
             with warnings.catch_warnings():
                 warnings.simplefilter('error', SyntaxWarning)
-                res = compile_command(''.join(lines), symbol='exec')
+                compile_command(''.join(lines), symbol='exec')
         except (SyntaxError, OverflowError, ValueError, TypeError,
                 MemoryError, SyntaxWarning):
             return 'invalid', None
         else:
-            if res is None:
+            if len(lines) > 1 and not lines[-1].strip().endswith(':') \
+                              and not lines[-2][:-1].endswith('\\'):
                 return 'incomplete', find_last_indent(lines)
         return 'complete', None
 


Test patch:
diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py
--- a/IPython/core/tests/test_inputtransformer2.py
+++ b/IPython/core/tests/test_inputtransformer2.py
@@ -10,6 +10,8 @@
 from IPython.core import inputtransformer2 as ipt2
 from IPython.core.inputtransformer2 import make_tokens_by_line
 
+from textwrap import dedent
+
 MULTILINE_MAGIC = ("""\
 a = f()
 %foo \\
@@ -206,6 +208,15 @@ def test_check_complete():
     nt.assert_equal(cc("a = '''\n   hi"), ('incomplete', 3))
     nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None))
     nt.assert_equal(cc("a \\ "), ('invalid', None))  # Nothing allowed after backslash
+    nt.assert_equal(cc("1\\\n+2"), ('complete', None))
+
+    example = dedent("""
+        if True:
+            a=1""" )
+
+    nt.assert_equal(cc(example), ('incomplete', 4))
+    nt.assert_equal(cc(example+'\n'), ('complete', None))
+    nt.assert_equal(cc(example+'\n    '), ('complete', None))
 
     # no need to loop on all the letters/numbers.
     short = '12abAB'+string.printable[62:]