Repository: spyder-ide/spyder

Issue:
Identation broken after statements with closing parenthesis followed by more code
## Description of your problem

Indentation is broken, if a line contains a closing parenthesis followed by more code. 

Additionally, comments seem to break the behaviour of `<enter>` auto-indentation as well.

## Reproducible examples

In the following screenshot the "weird indentation" was produced by automatic indentation when pressing "Enter":

![spyder-indent-1](https://cloud.githubusercontent.com/assets/7498468/22425241/7254c3d8-e6fa-11e6-9b72-b27aa9b45d46.png)





## Versions and main components

* Spyder Version: 3.1.2
* Python Version: `3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]`
* Operating system: Windows 10 Professional, 64bit


## Dependencies

```
jedi >=0.8.1     :  0.9.0 (OK)
matplotlib >=1.0 :  1.5.3 (OK)
nbconvert >=4.0  :  5.1.1 (OK)
numpy >=1.7      :  1.11.1 (OK)
pandas >=0.13.1  :  0.18.1 (OK)
pep8 >=0.6       :  1.7.0 (OK)
pyflakes >=0.6.0 :  1.5.0 (OK)
pygments >=2.0   :  2.2.0 (OK)
pylint >=0.25    :  1.6.5 (OK)
qtconsole >=4.2.0:  4.2.1 (OK)
rope >=0.9.4     :  0.9.4-1 (OK)
sphinx >=0.6.6   :  1.4.6 (OK)
sympy >=0.7.3    :  1.0 (OK)
```


Discussion:
@goanpeca, let's use `3.1.{3,4,etc}` for really critical things, or things that we didn't finished on time for 3.1 but were almost ready.

All the rest has to go to `3.2`. Else we could end up fixing bugs for new things we introduce in the hotfix branch :-)
Related: Is it possible to disable automatic indentation entirely?
> Related: Is it possible to disable automatic indentation entirely?

No, It isn't possible

Also, I'm working in the issues that you mentioned, It'll be fixed in v3.2

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
@@ -1985,9 +1985,36 @@ def fix_indent(self, forward=True, comment_or_string=False):
                 else:
                     correct_indent -= len(self.indent_chars)
             elif len(re.split(r'\(|\{|\[', prevtext)) > 1:
+
+                # Check if all braces are matching using a stack
+                stack = ['dummy']  # Dummy elemet to avoid index errors
+                deactivate = None
+                for c in prevtext:
+                    if deactivate is not None:
+                        if c == deactivate:
+                            deactivate = None
+                    elif c in ["'", '"']:
+                        deactivate = c
+                    elif c in ['(', '[','{']:
+                        stack.append(c)
+                    elif c == ')' and stack[-1] == '(':
+                        stack.pop()
+                    elif c == ']' and stack[-1] == '[':
+                        stack.pop()
+                    elif c == '}' and stack[-1] == '{':
+                        stack.pop()
+
+                if len(stack) == 1:  # all braces matching
+                    pass
+
                 # Hanging indent
                 # find out if the last one is (, {, or []})
-                if re.search(r'[\(|\{|\[]\s*$', prevtext) is not None:
+                # only if prevtext is long that the hanging indentation
+                elif (re.search(r'[\(|\{|\[]\s*$', prevtext) is not None and
+                      ((self.indent_chars == '\t' and
+                        self.tab_stop_width_spaces * 2 < len(prevtext)) or
+                       (self.indent_chars.startswith(' ') and
+                        len(self.indent_chars) * 2 < len(prevtext)))):
                     if self.indent_chars == '\t':
                         correct_indent += self.tab_stop_width_spaces * 2
                     else:
@@ -2368,9 +2395,11 @@ def kill_next_word(self):
         self.setTextCursor(cursor)
 
     #------Autoinsertion of quotes/colons
-    def __get_current_color(self):
+    def __get_current_color(self, cursor=None):
         """Get the syntax highlighting color for the current cursor position"""
-        cursor = self.textCursor()
+        if cursor is None:
+            cursor = self.textCursor()
+
         block = cursor.block()
         pos = cursor.position() - block.position()  # relative pos within block
         layout = block.layout()
@@ -2392,10 +2421,14 @@ def __get_current_color(self):
         else:
             return None
 
-    def in_comment_or_string(self):
+    def in_comment_or_string(self, cursor=None):
         """Is the cursor inside or next to a comment or string?"""
         if self.highlighter:
-            current_color = self.__get_current_color()
+            if cursor is None:
+                current_color = self.__get_current_color()
+            else:
+                current_color = self.__get_current_color(cursor=cursor)
+
             comment_color = self.highlighter.get_color_name('comment')
             string_color = self.highlighter.get_color_name('string')
             if (current_color == comment_color) or (current_color == string_color):
@@ -2698,7 +2731,20 @@ def keyPressEvent(self, event):
                    and self.codecompletion_enter:
                     self.select_completion_list()
                 else:
-                    cmt_or_str = self.in_comment_or_string()
+                    # Check if we're in a comment or a string at the
+                    # current position
+                    cmt_or_str_cursor = self.in_comment_or_string()
+
+                    # Check if the line start with a comment or string
+                    cursor = self.textCursor()
+                    cursor.setPosition(cursor.block().position(),
+                                       QTextCursor.KeepAnchor)
+                    cmt_or_str_line_begin = self.in_comment_or_string(
+                                                cursor=cursor)
+
+                    # 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()
                     TextEditBaseWidget.keyPressEvent(self, event)
                     self.fix_indent(comment_or_string=cmt_or_str)


Test patch:
diff --git a/spyder/plugins/tests/test_ipythonconsole.py b/spyder/plugins/tests/test_ipythonconsole.py
--- a/spyder/plugins/tests/test_ipythonconsole.py
+++ b/spyder/plugins/tests/test_ipythonconsole.py
@@ -71,6 +71,7 @@ def test_load_kernel_file_from_id(ipyconsole, qtbot):
     ipyconsole.close()
 
 
+@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
 def test_load_kernel_file_from_location(ipyconsole, qtbot):
     """
     Test that a new client is created using a connection file
@@ -95,6 +96,7 @@ def test_load_kernel_file_from_location(ipyconsole, qtbot):
     ipyconsole.close()
 
 
+@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
 def test_load_kernel_file(ipyconsole, qtbot):
     """
     Test that a new client is created using the connection file
diff --git a/spyder/widgets/sourcecode/tests/test_autoindent.py b/spyder/widgets/sourcecode/tests/test_autoindent.py
--- a/spyder/widgets/sourcecode/tests/test_autoindent.py
+++ b/spyder/widgets/sourcecode/tests/test_autoindent.py
@@ -153,6 +153,14 @@ def test_first_line():
          "test_commented_brackets"),
         ("s = a[(a['a'] == l) & (a['a'] == 1)]['a']\n", "s = a[(a['a'] == l) & (a['a'] == 1)]['a']\n",
          "test_balanced_brackets"),
+        ("a = (a  #  some comment\n", "a = (a  #  some comment\n     ", "test_inline_comment"),
+        ("len(a) == 1\n", "len(a) == 1\n", "test_balanced_brackets_not_ending_in_bracket"),
+        ("x = f(\n", "x = f(\n      ", "test_short_open_bracket_not_hanging_indent"),
+
+        pytest.mark.xfail(
+            ("foo = 1  # Comment open parenthesis (\n",
+             "foo = 1  # Comment open parenthesis (\n",
+             "test_comment_with parenthesis")),
     ])
 def test_indentation_with_spaces(text_input, expected, test_text):
     text = get_indent_fix(text_input)
@@ -178,7 +186,7 @@ def test_def_with_unindented_comment():
          "test with indented comment"),
         ("def function():\n\tprint []\n", "def function():\n\tprint []\n\t",
          "test brackets alone"),
-        ("\na = {\n", "\na = {\n\t\t", "indentation after opening bracket"),
+        ("\nsome_long_name = {\n", "\nsome_long_name = {\n\t\t", "indentation after opening bracket"),
         ("def function():\n", "def function():\n\t", "test simple def"),
         ("open_parenthesis(\n", "open_parenthesis(\n\t\t",
          "open parenthesis"),
@@ -191,6 +199,9 @@ def test_def_with_unindented_comment():
         pytest.mark.xfail(
             ("def function():\n# Comment\n", "def function():\n# Comment\n\t",
              "test_def_with_unindented_comment")),
+        pytest.mark.xfail(
+            ("a = (a  #  some comment\n", "a = (a  #  some comment\n\t ",
+             "test_inline_comment")),
     ])
 def test_indentation_with_tabs(text_input, expected, test_text,
                                tab_stop_width_spaces):