Repository: ipython/ipython

Issue:
In the PTK terminal, control-o should indent the newly created line
Suppose I have:

```
In [5]: def f():
   ...:     [CURSOR]a = 1           
```

If I press control-O to open up a new line, I get:

```
In [5]: def f():
   ...:     [CURSOR]
   ...: a = 1
```

I would prefer to get:

```
In [5]: def f():
   ...:     [CURSOR]
   ...:     a = 1
```



Discussion:
I'm going to tag that as "new feature", that would be one more carrot for Python 3 once implemented :-P. 
I might have a got at that before but not promises !

@njsmith -- what should happen if you have 

``` console
In [5]: def f():
   ...:     a = 1[CURSOR]
```

and hit Ctrl-O.  Do you expect a dedent or a preserved indentation level?

I guess it doesn't matter that much? But an indent seems slightly more convenient?

I guess the general proposal would be: hitting control-O should be equivalent to hitting Enter, except that: (a) it should never cause the current block to be executed, (b) it should leave the cursor where it started.

THis has been partially addressed by #9770 (well, all things listed in here seem to work). 
The one which is weird is : 

```
def foo():[CURSOR]
    a = 1$
````
`Ctrl-O` end up with:

```
def foo():[CURSOR]
$
    a = 1$
```
Which is still copying indent (if it's a method you are still at class level at least).
But we appear to have the logic for dedenting one level after `pass`, `return` and indenting one more after `:` so we should be able to improve. 

 



Resolving patch:
diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py
--- a/IPython/terminal/shortcuts.py
+++ b/IPython/terminal/shortcuts.py
@@ -1,5 +1,16 @@
+"""
+Module to define and register Terminal IPython shortcuts with
+:any:`prompt_toolkit`
+"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import warnings
 import signal
 import sys
+from typing import Callable
+
 
 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
 from prompt_toolkit.filters import (HasFocus, HasSelection, Condition,
@@ -60,7 +71,7 @@ def register_ipython_shortcuts(registry, shell):
 
     registry.add_binding(Keys.ControlO,
                          filter=(HasFocus(DEFAULT_BUFFER)
-                                & EmacsInsertMode()))(newline_with_copy_margin)
+                                & EmacsInsertMode()))(newline_autoindent_outer(shell.input_splitter))
 
     registry.add_binding(Keys.F2,
                          filter=HasFocus(DEFAULT_BUFFER)
@@ -166,11 +177,20 @@ def force_exit(event):
 def indent_buffer(event):
     event.current_buffer.insert_text(' ' * 4)
 
+@undoc
 def newline_with_copy_margin(event):
     """
+    DEPRECATED since IPython 6.0
+
+    See :any:`newline_autoindent_outer` for a replacement.
+
     Preserve margin and cursor position when using
     Control-O to insert a newline in EMACS mode
     """
+    warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
+      "see `newline_autoindent_outer(shell)(event)` for a replacement.",
+                  DeprecationWarning, stacklevel=2)
+
     b = event.current_buffer
     cursor_start_pos = b.document.cursor_position_col
     b.newline(copy_margin=True)
@@ -180,6 +200,30 @@ def newline_with_copy_margin(event):
         pos_diff = cursor_start_pos - cursor_end_pos
         b.cursor_right(count=pos_diff)
 
+def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
+    """
+    Return a function suitable for inserting a indented newline after the cursor.
+
+    Fancier version of deprecated ``newline_with_copy_margin`` which should
+    compute the correct indentation of the inserted line. That is to say, indent
+    by 4 extra space after a function definition, class definition, context
+    manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
+    """
+
+    def newline_autoindent(event):
+        """insert a newline after the cursor indented appropriately."""
+        b = event.current_buffer
+        d = b.document
+
+        if b.complete_state:
+            b.cancel_completion()
+        text = d.text[:d.cursor_position] + '\n'
+        _, indent = inputsplitter.check_complete(text)
+        b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
+
+    return newline_autoindent
+
+
 def open_input_in_editor(event):
     event.cli.current_buffer.tempfile_suffix = ".py"
     event.cli.current_buffer.open_in_editor(event.cli)


Test patch: