Repository: spyder-ide/spyder

Issue:
5.1.1 : omitted indentation when pasting with the editor
Installed 5.1.1 and reset to factory defaults.
Then, I created a small function:
```
def f():
    x = 1
    y = 2
```
I wanted to add another assignment just after ```x = 1```.  So, I copied that entire line and pasted it.  This was the result:
```
def f():
    x = 1
x = 1
    y = 2
```
Uninstalled 5.1.1 and installed 5.0.5.  Then, I reset to factory defaults and created the same small function:
```
def f():
    x = 1
    y = 2
```
Then, I copied that entire ```x = 1``` line and pasted it.  This was the result:
```
def f():
    x = 1
    x = 1
    y = 2
```

IMO, the 5.0.5 editor behaved correctly but the 5.1.1 editor neglected to paste in the indentation.


Discussion:
Hey @texadactyl, thanks for reporting. I can reproduce this error.

@impact27, could you take a look at this? 
The expected behaviour is that it will correct the indentation to where your cursor is. If you add a line below `x = 1` you should get (`|` is the cursor):
```
def f():
    x = 1
    |
    y = 2
```
If you then paste you should get:
```
def f():
    x = 1
    x = 1
    y = 2
```
If instead you want to de-dent, you should, from:
```
def f():
    x = 1
    |
    y = 2
```
Press delete:
```
def f():
    x = 1
|
    y = 2
```
Then paste:
```
def f():
    x = 1
x = 1
    y = 2
```
The new logic tries pastes the code in the clipboard on the indentation your cursor is. 
@impact27, if the first line a user is trying to paste contains some indentation, I think we should preserve it, and adjust the indentation of all other lines according to that.

What do you think? 
Folks are used to the spyder editor as it was through 5.0.5.  The 5.1.1 editor-paste behaviour will require a lot of users to re-think the way they do copy/cut and paste of large blocks of code.  Small segments are no big deal.

I do not copy/cut and paste a large block of code spanning multiple lines the way you showed.  I'd much rather do a large collection of lines in a line-oriented fashion (cursor on first column).  Less chance of a mess.  If I need to indent or unindent afterwards, I can plan for that.

However, there must be some benefit for this change or you would not have made it.  I'll try to figure that out.

Not trying to give you a load of re-do.

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -2803,33 +2803,46 @@ def paste(self):
         first_line_selected, *remaining_lines = (text + eol_chars).splitlines()
         first_line = preceding_text + first_line_selected
 
-        lines_adjustment = CLIPBOARD_HELPER.remaining_lines_adjustment(
-            preceding_text)
-
-        if preceding_text.strip() == "" and first_line.strip != "":
-            # The indent is controlled by preceding_text. Remove extra indent
-            extra_indent = self.get_line_indentation(first_line_selected)
-            lines_adjustment -= extra_indent
-            first_line = self.adjust_indentation(
-                first_line, -extra_indent)
-
-        # Fix indentation of multiline text
-        if self.is_python_like() and len(preceding_text.strip()) == 0:
+        first_line_adjustment = 0
+
+        # Dedent if automatic indentation makes code invalid
+        # Minimum indentation = max of current and paster indentation
+        if (self.is_python_like() and len(preceding_text.strip()) == 0
+                and len(first_line.strip()) > 0):
             # Correct indentation
             desired_indent = self.find_indentation()
             if desired_indent:
+                # minimum indentation is either the current indentation
+                # or the indentation of the paster text
+                desired_indent = max(
+                    desired_indent,
+                    self.get_line_indentation(first_line_selected),
+                    self.get_line_indentation(preceding_text))
                 first_line_adjustment = (
                     desired_indent - self.get_line_indentation(first_line))
-                if first_line_adjustment < 0:
-                    # Only dedent, don't indent
-                    lines_adjustment += first_line_adjustment
-                    first_line = self.adjust_indentation(
-                        first_line, first_line_adjustment)
-
-        # Get new text
-        remaining_lines = [
-            self.adjust_indentation(line, lines_adjustment)
-            for line in remaining_lines]
+                # Only dedent, don't indent
+                first_line_adjustment = min(first_line_adjustment, 0)
+                # Only dedent, don't indent
+                first_line = self.adjust_indentation(
+                    first_line, first_line_adjustment)
+
+        # Fix indentation of multiline text based on first line
+        if len(remaining_lines) > 0 and len(first_line.strip()) > 0:
+            lines_adjustment = first_line_adjustment
+            lines_adjustment += CLIPBOARD_HELPER.remaining_lines_adjustment(
+                preceding_text)
+
+            # Make sure the code is not flattened
+            max_dedent = min(
+                [self.get_line_indentation(line)
+                 for line in remaining_lines if line.strip() != ""])
+            lines_adjustment = max(lines_adjustment, -max_dedent)
+    
+            # Get new text
+            remaining_lines = [
+                self.adjust_indentation(line, lines_adjustment)
+                for line in remaining_lines]
+
         text = eol_chars.join([first_line, *remaining_lines])
 
         self.skip_rstrip = True


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_codeeditor_1.py b/spyder/plugins/editor/widgets/tests/test_codeeditor_1.py
--- a/spyder/plugins/editor/widgets/tests/test_codeeditor_1.py
+++ b/spyder/plugins/editor/widgets/tests/test_codeeditor_1.py
@@ -159,26 +159,57 @@ def test_paste_text(code_editor_bot, text, line_ending_char):
 def test_copy_paste_autoindent(code_editor_bot):
     """Test copy pasting text into the editor at different indent."""
     editor = code_editor_bot[0]
-    text = ("if a:\n    b\n    if c:\n        d\n    e\n")
-    expected_text = ("if a:\n    b\n    d\ne\n    if c:\n        d\n    e\n")
+    text = ("if a:\n    b\n    if c:\n        d\n    if e:\n        f\n")
     editor.set_text(text)
     # Copy
     cursor = editor.textCursor()
     cursor.setPosition(30)
-    cursor.setPosition(45, QTextCursor.KeepAnchor)
+    cursor.setPosition(59, QTextCursor.KeepAnchor)
     editor.setTextCursor(cursor)
     cb = QApplication.clipboard()
-    cb.setText("d\n    e", mode=cb.Clipboard)
+    cb.setText("d\n    if e:\n        f", mode=cb.Clipboard)
     editor.copy()
+
+    d = {
+        0: "if a:\n    b\nd\nif e:\n    f\n    if c:\n        d\n    if e:\n        f\n",
+        4: "if a:\n    b\n    d\nif e:\n    f\n    if c:\n        d\n    if e:\n        f\n",
+        8: "if a:\n    b\n        d\n    if e:\n        f\n    if c:\n        d\n    if e:\n        f\n",
+        }
+    # Paste
+    for indent in d.keys():
+        editor.set_text(text)
+        cursor = editor.textCursor()
+        cursor.setPosition(11)
+        cursor.insertText("\n" + indent * ' ')
+        editor.setTextCursor(cursor)
+        editor.paste()
+        assert editor.toPlainText() == d[indent]
+
+    # Same thing but with copying the spaces
+    editor.set_text(text)
+    # Copy
+    cursor = editor.textCursor()
+    cursor.setPosition(30-4)
+    cursor.setPosition(59, QTextCursor.KeepAnchor)
+    editor.setTextCursor(cursor)
+    cb = QApplication.clipboard()
+    cb.setText("    d\n    if e:\n        f", mode=cb.Clipboard)
+    editor.copy()
+
+    d = {
+        0: "if a:\n    b\n    d\nif e:\n    f\n    if c:\n        d\n    if e:\n        f\n",
+        4: "if a:\n    b\n    d\nif e:\n    f\n    if c:\n        d\n    if e:\n        f\n",
+        8: "if a:\n    b\n        d\n    if e:\n        f\n    if c:\n        d\n    if e:\n        f\n",
+        }
     # Paste
-    for indent in [4, 8]:
+    for indent in d.keys():
         editor.set_text(text)
         cursor = editor.textCursor()
         cursor.setPosition(11)
         cursor.insertText("\n" + indent * ' ')
         editor.setTextCursor(cursor)
         editor.paste()
-        assert editor.toPlainText() == expected_text
+        assert editor.toPlainText() == d[indent]
 
 
 if __name__ == "__main__":
diff --git a/spyder/plugins/editor/widgets/tests/test_editor_and_outline.py b/spyder/plugins/editor/widgets/tests/test_editor_and_outline.py
--- a/spyder/plugins/editor/widgets/tests/test_editor_and_outline.py
+++ b/spyder/plugins/editor/widgets/tests/test_editor_and_outline.py
@@ -375,8 +375,6 @@ def test_editor_outlineexplorer(qtbot, completions_codeeditor_outline):
         qtbot.keyPress(code_editor, Qt.Key_Return)
 
     qtbot.keyPress(code_editor, Qt.Key_Up)
-    # Add a tab for correct indentation
-    qtbot.keyPress(code_editor, Qt.Key_Tab)
     code_editor.paste()
     qtbot.wait(3000)