Repository: ipython/ipython

Issue:
END key inserts autosuggestion in the middle of a line
Hitting END in the middle of a line inserts an autosuggestion at the cursor instead of the end of the line.

From a new IPython profile (hence ensuring no history), type these keystrokes:

```
123456789<Enter>
123456<End><Enter>
123456<Left arrow><Left arrow><Left arrow><End><Enter>
```

This produces the transcript:

```
In [1]: 123456789
Out[1]: 123456789

In [2]: 123456789
Out[2]: 123456789

In [3]: 123789456
Out[3]: 123789456
```

Actual behavior: The final input is "123789456", which wasn't present in the history before.

Expected behavior: Hitting END when the cursor is in the middle of "123456" should either leave the input as-is, or transform it to "123456789".

@meeseeksdev autosuggestions


Discussion:

Resolving patch:
diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py
--- a/IPython/terminal/shortcuts.py
+++ b/IPython/terminal/shortcuts.py
@@ -32,6 +32,22 @@ def cursor_in_leading_ws():
     return (not before) or before.isspace()
 
 
+# Needed for to accept autosuggestions in vi insert mode
+def _apply_autosuggest(event):
+    """
+    Apply autosuggestion if at end of line.
+    """
+    b = event.current_buffer
+    d = b.document
+    after_cursor = d.text[d.cursor_position :]
+    lines = after_cursor.split("\n")
+    end_of_current_line = lines[0].strip()
+    suggestion = b.suggestion
+    if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
+        b.insert_text(suggestion.text)
+    else:
+        nc.end_of_line(event)
+
 def create_ipython_shortcuts(shell):
     """Set up the prompt_toolkit keyboard shortcuts for IPython"""
 
@@ -267,15 +283,6 @@ def ebivim():
 
     focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
 
-    # Needed for to accept autosuggestions in vi insert mode
-    def _apply_autosuggest(event):
-        b = event.current_buffer
-        suggestion = b.suggestion
-        if suggestion is not None and suggestion.text:
-            b.insert_text(suggestion.text)
-        else:
-            nc.end_of_line(event)
-
     @kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))
     def _(event):
         _apply_autosuggest(event)
diff --git a/tools/retar.py b/tools/retar.py
--- a/tools/retar.py
+++ b/tools/retar.py
@@ -4,6 +4,8 @@
 usage: 
 
     $ export SOURCE_DATE_EPOCH=$(date +%s)
+    # or
+    $ export SOURCE_DATE_EPOCH=$(git show -s --format=%ct HEAD)
     ...
     $ python retar.py <tarfile.gz>
 


Test patch:
diff --git a/IPython/tests/test_shortcuts.py b/IPython/tests/test_shortcuts.py
new file mode 100644
--- /dev/null
+++ b/IPython/tests/test_shortcuts.py
@@ -0,0 +1,40 @@
+import pytest
+from IPython.terminal.shortcuts import _apply_autosuggest
+
+from unittest.mock import Mock
+
+
+def make_event(text, cursor, suggestion):
+    event = Mock()
+    event.current_buffer = Mock()
+    event.current_buffer.suggestion = Mock()
+    event.current_buffer.cursor_position = cursor
+    event.current_buffer.suggestion.text = suggestion
+    event.current_buffer.document = Mock()
+    event.current_buffer.document.get_end_of_line_position = Mock(return_value=0)
+    event.current_buffer.document.text = text
+    event.current_buffer.document.cursor_position = cursor
+    return event
+
+
+@pytest.mark.parametrize(
+    "text, cursor, suggestion, called",
+    [
+        ("123456", 6, "123456789", True),
+        ("123456", 3, "123456789", False),
+        ("123456   \n789", 6, "123456789", True),
+    ],
+)
+def test_autosuggest_at_EOL(text, cursor, suggestion, called):
+    """
+    test that autosuggest is only applied at end of line.
+    """
+
+    event = make_event(text, cursor, suggestion)
+    event.current_buffer.insert_text = Mock()
+    _apply_autosuggest(event)
+    if called:
+        event.current_buffer.insert_text.assert_called()
+    else:
+        event.current_buffer.insert_text.assert_not_called()
+        # event.current_buffer.document.get_end_of_line_position.assert_called()