Repository: spyder-ide/spyder

Issue:
Can't indent code blocks more than once (using tab) on Spyder 4
I'm on spyder4 dev master and can't tab indent a block of code that has any leading spaces and therefore it won't let me indent more than one tab. Shift tab works multiple times to remove indents.

I bisected this to #3958 ...I guess I should have tested better.

**What steps will reproduce the problem?**

1. highlight a block of code
2. press tab once (works)
3. press tab again (nothing)

## Versions and main components

* Spyder Version: 4 master
* Python Version: 3.6
* Qt Version: 5.6.2
* PyQt Version: 5.6
* Operating system: Linux


## Dependencies

IPython >=4.0    :  6.1.0 (OK)
cython >=0.21    :  0.25.2 (OK)
jedi >=0.9.0     :  0.10.2 (OK)
nbconvert >=4.0  :  4.2.0 (OK)
numpy >=1.7      :  1.12.1 (OK)
pandas >=0.13.1  :  0.19.2 (OK)
psutil >=0.3     :  5.0.1 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pyflakes >=0.6.0 :  1.5.0 (OK)
pygments >=2.0   :  2.1.3 (OK)
pylint >=0.25    :  1.6.4 (OK)
qtconsole >=4.2.0:  4.3.0 (OK)
rope >=0.9.4     :  0.9.4-1 (OK)
sphinx >=0.6.6   :  1.5.1 (OK)
sympy >=0.7.3    :  1.0 (OK)




Discussion:
> I guess I should have tested better.

Don't worry about it. Thanks for letting us know this is a side effect of that PR.

@dalthviz, please work on this one.

Resolving patch:
diff --git a/spyder/widgets/sourcecode/codeeditor.py b/spyder/widgets/sourcecode/codeeditor.py
--- a/spyder/widgets/sourcecode/codeeditor.py
+++ b/spyder/widgets/sourcecode/codeeditor.py
@@ -1559,6 +1559,8 @@ def add_prefix(self, prefix):
                                         QTextCursor.MoveAnchor,
                                         move_number)
                     cursor.insertText(prefix)
+                elif '#' not in prefix:
+                    cursor.insertText(prefix)
                 if start_pos == 0 and cursor.blockNumber() == 0:
                     # Avoid infinite loop when indenting the very first line
                     break


Test patch:
diff --git a/spyder/widgets/sourcecode/tests/test_indentation.py b/spyder/widgets/sourcecode/tests/test_indentation.py
new file mode 100644
--- /dev/null
+++ b/spyder/widgets/sourcecode/tests/test_indentation.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © Spyder Project Contributors
+# Licensed under the terms of the MIT License
+#
+
+"""
+Tests for the indentation feature
+"""
+
+# Third party imports
+import pytest
+from qtpy.QtGui import QTextCursor
+
+# Local imports
+from spyder.py3compat import to_text_string
+from spyder.widgets.sourcecode.codeeditor import CodeEditor
+
+
+# --- Helper methods
+# -----------------------------------------------------------------------------
+def make_indent(editor, single_line=True, start_line=1):
+    """Indent and return code."""
+    editor.go_to_line(start_line)
+    if not single_line:
+        editor.moveCursor(QTextCursor.End, mode=QTextCursor.KeepAnchor)
+    editor.indent()
+    text = editor.toPlainText()
+    return to_text_string(text)
+
+
+# --- Fixtures
+# -----------------------------------------------------------------------------
+@pytest.fixture
+def code_editor_indent_bot(qtbot):
+    """
+    Setup CodeEditor with some text useful for folding related tests.
+    """
+    editor = CodeEditor(parent=None)
+    indent_chars = " " * 2
+    tab_stop_width_spaces = 4
+    language = "Python"
+    editor.setup_editor(language=language, indent_chars=indent_chars,
+                        tab_stop_width_spaces=tab_stop_width_spaces)
+
+    return editor, qtbot
+
+
+# --- Tests
+# -----------------------------------------------------------------------------
+def test_single_line_indent(code_editor_indent_bot):
+    """Test indentation in a single line."""
+    editor, qtbot = code_editor_indent_bot
+    text = ("class a():\n"
+            "self.b = 1\n"
+            "print(self.b)\n"
+            "\n"
+            )
+    editor.set_text(text)
+    # Indent line without spaces
+    text = make_indent(editor, start_line=2)
+    assert text == ("class a():\n"
+                    "  self.b = 1\n"
+                    "print(self.b)\n"
+                    "\n"
+                    )
+
+
+def test_selection_indent(code_editor_indent_bot):
+    """Test indentation with selection of more tha one line."""
+    editor, qtbot = code_editor_indent_bot
+    text = ("class a():\n"
+            "self.b = 1\n"
+            "print(self.b)\n"
+            "\n"
+            )
+    editor.set_text(text)
+    # Toggle manually commented code
+    text = make_indent(editor, single_line=False, start_line=2)
+    assert text == ("class a():\n"
+                    "  self.b = 1\n"
+                    "  print(self.b)\n"
+                    "  \n"
+                    )
+
+
+if __name__ == "__main__":
+    pytest.main()