Repository: spyder-ide/spyder

Issue:
Indentation level is not reset when writing a comment after indent within a long instruction
### Problem Description

When breaking up a long instruction into several lines to comply with the style guide then indentation is necessary.

After entering the instruction and hitting enter the cursor will be at the last used indentation level. Hitting backspace resets that for this and future empty lines and lines of code, however not for following comment lines.

I have switched off the option "Automatic indentation after 'else', 'elif', etc." and it happens nonetheless.

### What steps reproduce the problem?

After entering the test variable in a file in the editor hit enter and backspace to reset the indentation level to zero, then enter the following lines manually:

This works fine:
```
this_is_a_test_variable\
    = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
a = 1
# this comment is fine, hit enter for next line
# this comment is fine too
```

This does not:
```
this_is_a_test_variable\
    = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
# this comment was written after resetting indentation, hit enter for next line
    # this line is indented again
```

## Versions
* Spyder version: 4.0.0
* Python version: 3.7.3
* Qt version: 5.12.5
* PyQt version: 5.12.3
* Operating System name/version: Windows 10

### Dependencies
```
cloudpickle >=0.5.0          :  1.2.2 (OK)
pygments >=2.0               :  2.5.2 (OK)
qtconsole >=4.6.0            :  4.6.0 (OK)
nbconvert >=4.0              :  5.6.1 (OK)
sphinx >=0.6.6               :  2.2.2 (OK)
pylint >=0.25                :  2.4.4 (OK)
psutil >=0.3                 :  5.6.7 (OK)
qtawesome >=0.5.7            :  0.6.0 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
zmq >=17                     :  18.1.1 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.8.1;<2.0.0:  1.8.1 (OK)
qdarkstyle >=2.7             :  2.7 (OK)
atomicwrites >=1.2.0         :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
intervaltree                 :  None (OK)
watchdog                     :  None (OK)
keyring                      :  None (OK)
pexpect >=4.4.0              :  4.7.0 (OK)
pympler                      :  None (OK)
sympy >=0.7.3                :  1.4 (OK)
cython >=0.21                :  0.29.14 (OK)
IPython >=4.0                :  7.10.1 (OK)
matplotlib >=2.0.0           :  3.1.2 (OK)
pandas >=0.13.1              :  0.25.3 (OK)
numpy >=1.7                  :  1.17.3 (OK)
scipy >=0.17.0               :  1.3.1 (OK)
pyls >=0.31.2;<0.32.0        :  0.31.2 (OK)
rtree >=0.8.3                :  0.9.1 (OK)

```



Discussion:
@impact27, could you help us with this one?

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
@@ -2564,7 +2564,7 @@ def process_todo(self, todo_results):
             data.todo = ''
 
         for message, line_number in todo_results:
-            block = self.document().findBlockByNumber(line_number-1)
+            block = self.document().findBlockByNumber(line_number - 1)
             data = block.userData()
             if not data:
                 data = BlockUserData(self)
@@ -2854,7 +2854,7 @@ def simple_indentation(self, forward=True, **kwargs):
         """
         cursor = self.textCursor()
         block_nb = cursor.blockNumber()
-        prev_block = self.document().findBlockByNumber(block_nb-1)
+        prev_block = self.document().findBlockByNumber(block_nb - 1)
         prevline = to_text_string(prev_block.text())
 
         indentation = re.match(r"\s*", prevline).group()
@@ -2865,7 +2865,8 @@ def simple_indentation(self, forward=True, **kwargs):
         cursor.insertText(indentation)
         return False  # simple indentation don't fix indentation
 
-    def fix_indent_smart(self, forward=True, comment_or_string=False):
+    def fix_indent_smart(self, forward=True, comment_or_string=False,
+                         cur_indent=None):
         """
         Fix indentation (Python only, no text selection)
 
@@ -2877,6 +2878,12 @@ def fix_indent_smart(self, forward=True, comment_or_string=False):
         comment_or_string: Do not adjust indent level for
             unmatched opening brackets and keywords
 
+        max_blank_lines: maximum number of blank lines to search before giving
+            up
+
+        cur_indent: current indent. This is the indent before we started
+            processing. E.g. when returning, indent before rstrip.
+
         Returns True if indent needed to be fixed
 
         Assumes self.is_python_like() to return True
@@ -2889,16 +2896,15 @@ def fix_indent_smart(self, forward=True, comment_or_string=False):
         add_indent = 0  # How many levels of indent to add
         prevline = None
         prevtext = ""
+        empty_lines = True
 
         closing_brackets = []
-        for prevline in range(block_nb-1, -1, -1):
+        for prevline in range(block_nb - 1, -1, -1):
             cursor.movePosition(QTextCursor.PreviousBlock)
             prevtext = to_text_string(cursor.block().text()).rstrip()
 
             bracket_stack, closing_brackets, comment_pos = self.__get_brackets(
                 prevtext, closing_brackets)
-            if comment_pos > -1:
-                prevtext = prevtext[:comment_pos].rstrip()
 
             if not prevtext:
                 continue
@@ -2912,6 +2918,15 @@ def fix_indent_smart(self, forward=True, comment_or_string=False):
             if bracket_stack or not closing_brackets:
                 break
 
+            if prevtext.strip() != '':
+                empty_lines = False
+
+        if empty_lines and prevline is not None and prevline < block_nb - 2:
+            # The previous line is too far, ignore
+            prevtext = ''
+            prevline = block_nb - 2
+            line_in_block = False
+
         # splits of prevtext happen a few times. Let's just do it once
         words = re.split(r'[\s\(\[\{\}\]\)]', prevtext.lstrip())
 
@@ -2959,8 +2974,15 @@ def fix_indent_smart(self, forward=True, comment_or_string=False):
 
         # TODO untangle this block
         if prevline and not bracket_stack and not prevtext.endswith(':'):
-            cur_indent = self.get_block_indentation(block_nb - 1)
-            is_blank = not self.get_text_line(block_nb - 1).strip()
+            if forward:
+                # Keep indentation of previous line
+                ref_line = block_nb - 1
+            else:
+                # Find indentation context
+                ref_line = prevline
+            if cur_indent is None:
+                cur_indent = self.get_block_indentation(ref_line)
+            is_blank = not self.get_text_line(ref_line).strip()
             trailing_text = self.get_text_line(block_nb).strip()
             # If brackets are matched and no block gets opened
             # Match the above line's indent and nudge to the next multiple of 4
@@ -3717,6 +3739,10 @@ def insert_text(event):
                 elif self.is_completion_widget_visible():
                     self.select_completion_list()
                 else:
+                    self.textCursor().beginEditBlock()
+                    cur_indent = self.get_block_indentation(
+                        self.textCursor().blockNumber())
+                    insert_text(event)
                     # Check if we're in a comment or a string at the
                     # current position
                     cmt_or_str_cursor = self.in_comment_or_string()
@@ -3731,13 +3757,13 @@ def insert_text(event):
                     # Check if we are in a comment or a string
                     cmt_or_str = cmt_or_str_cursor and cmt_or_str_line_begin
 
-                    self.textCursor().beginEditBlock()
-                    insert_text(event)
                     if self.strip_trailing_spaces_on_modify:
                         self.fix_and_strip_indent(
-                            comment_or_string=cmt_or_str)
+                            comment_or_string=cmt_or_str,
+                            cur_indent=cur_indent)
                     else:
-                        self.fix_indent(comment_or_string=cmt_or_str)
+                        self.fix_indent(comment_or_string=cmt_or_str,
+                                        cur_indent=cur_indent)
                     self.textCursor().endEditBlock()
         elif key == Qt.Key_Insert and not shift and not ctrl:
             self.setOverwriteMode(not self.overwriteMode())
@@ -3882,14 +3908,18 @@ def _handle_completions(self):
                     self._last_key_pressed_text = ''
                     self._last_pressed_key = None
 
-    def fix_and_strip_indent(self, comment_or_string=False):
-        """Automatically fix indent and strip previous automatic indent."""
+    def fix_and_strip_indent(self, *args, **kwargs):
+        """
+        Automatically fix indent and strip previous automatic indent.
+
+        args and kwargs are forwarded to self.fix_indent
+        """
         # Fix indent
         cursor_before = self.textCursor().position()
         # A change just occured on the last line (return was pressed)
         if cursor_before > 0:
             self.last_change_position = cursor_before - 1
-        self.fix_indent(comment_or_string=comment_or_string)
+        self.fix_indent(*args, **kwargs)
         cursor_after = self.textCursor().position()
         # Remove previous spaces and update last_auto_indent
         nspaces_removed = self.strip_trailing_spaces()


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_autoindent.py b/spyder/plugins/editor/widgets/tests/test_autoindent.py
--- a/spyder/plugins/editor/widgets/tests/test_autoindent.py
+++ b/spyder/plugins/editor/widgets/tests/test_autoindent.py
@@ -179,7 +179,7 @@ def test_indentation_with_spaces():
 
 def test_def_with_unindented_comment():
     text = get_indent_fix("def function():\n# Comment\n")
-    assert text == "def function():\n# Comment\n    ", repr(text)
+    assert text == "def function():\n# Comment\n", repr(text)
 
 
 def test_issue_5606():
@@ -406,7 +406,7 @@ def test_indentation_with_tabs():
         ("def some_func():\n\treturns = 10\n",
          "def some_func():\n\treturns = 10\n\t",
          "test_return_not_keyword"),
-        ("def function():\n# Comment\n", "def function():\n# Comment\n\t",
+        ("def function():\n# Comment\n", "def function():\n# Comment\n",
          "test_def_with_unindented_comment")
         ]
 
diff --git a/spyder/plugins/editor/widgets/tests/test_codeeditor.py b/spyder/plugins/editor/widgets/tests/test_codeeditor.py
--- a/spyder/plugins/editor/widgets/tests/test_codeeditor.py
+++ b/spyder/plugins/editor/widgets/tests/test_codeeditor.py
@@ -142,6 +142,22 @@ def test_editor_log_lsp_handle_errors(editorbot, capsys):
          "('🚫')\n",
          [Qt.Key_Enter],
          True),
+        ("def fun():",
+         "def fun():\n\n    ",
+         [Qt.Key_Enter, Qt.Key_Enter],
+         True),
+        ("def fun():",
+         "def fun():\n\n\n",
+         [Qt.Key_Enter, Qt.Key_Enter, Qt.Key_Enter],
+         True),
+        ("def fun():\n    i = 0\n# no indent",
+         "def fun():\n    i = 0\n# no indent\n",
+         [Qt.Key_Enter],
+         True),
+        ("if a:\n    def b():\n        i = 1",
+         "if a:\n    def b():\n        i = 1\n\n    ",
+         [Qt.Key_Enter, Qt.Key_Enter, Qt.Key_Backspace],
+         True),
     ])
 def test_editor_rstrip_keypress(editorbot, input_text, expected_text, keys,
                                 strip_all):
@@ -233,5 +249,20 @@ def test_comment(editorbot):
     assert widget.toPlainText() == "import numpy"
 
 
+def test_undo_return(editorbot):
+    """Test that we can undo a return."""
+    qtbot, editor = editorbot
+    text = "if True:\n    0"
+    returned_text = "if True:\n    0\n    "
+    editor.set_text(text)
+    cursor = editor.textCursor()
+    cursor.setPosition(14)
+    editor.setTextCursor(cursor)
+    qtbot.keyPress(editor, Qt.Key_Return)
+    assert editor.toPlainText() == returned_text
+    qtbot.keyPress(editor, "z", modifier=Qt.ControlModifier)
+    assert editor.toPlainText() == text
+
+
 if __name__ == '__main__':
     pytest.main(['test_codeeditor.py'])