Repository: spyder-ide/spyder

Issue:
Close parenthesis when unmatched
## Description

When opening a parenthesis, spyder will automatically close it.
Because of that, when I close a parenthesis, it is automatically deleted.
But when I have unmatched parenthesis, I would like to be able to close without going to the end

example: (`|` is the cursor)
`fuc(fun(fuc(arg|))`

If I want to close this parenthesis in spyder, I must type `)))`
So is it possible to disable the parenthesis deletion when all the parenthesis are not matched on that line?

I know the parenthesis might be closed on another line but then I would not type `)`



## Version and main components

* Spyder Version: 3.2.5.dev0 c22566281
* Python Version: 3.6.2
* Qt Versions: 5.9.1, PyQt5 5.9 on Darwin

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.20.3 (OK)
numpy >=1.7      :  1.13.3 (OK)
sphinx >=0.6.6   :  1.6.4 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.0 (OK)
psutil >=0.3     :  5.3.1 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.27.1 (OK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.7.4 (OK)

```



Discussion:
This is a valid request, but I don't know how we could solve it in all possible cases.

@rlaverde, what do you think about it?
Yes, I have also experienced this issue. But I don't know how the logic of this works, I'll take a look to the logic and try to fix it.
I usually turn off bracket matching because of this.

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
@@ -2892,8 +2892,13 @@ def keyPressEvent(self, event):
             cursor.movePosition(QTextCursor.NextCharacter,
                                 QTextCursor.KeepAnchor)
             text = to_text_string(cursor.selectedText())
-            if text == {Qt.Key_ParenRight: ')', Qt.Key_BraceRight: '}',
-                        Qt.Key_BracketRight: ']'}[key]:
+            key_matches_next_char = (
+                text == {Qt.Key_ParenRight: ')', Qt.Key_BraceRight: '}',
+                         Qt.Key_BracketRight: ']'}[key]
+            )
+            if (key_matches_next_char
+              and not self.__unmatched_braces_in_line(cursor.block().text())):
+                # overwrite an existing brace if all braces in line are matched
                 cursor.clearSelection()
                 self.setTextCursor(cursor)
             else:


Test patch:
diff --git a/spyder/widgets/tests/test_codeeditor.py b/spyder/widgets/tests/test_codeeditor.py
--- a/spyder/widgets/tests/test_codeeditor.py
+++ b/spyder/widgets/tests/test_codeeditor.py
@@ -54,4 +54,38 @@ def test_editor_lower_to_upper(editorbot):
     widget.setTextCursor(cursor)
     widget.transform_to_uppercase()
     new_text = widget.get_text('sof', 'eof')
-    assert text != new_text
\ No newline at end of file
+    assert text != new_text
+
+def test_editor_complete_backet(editorbot):
+    qtbot, editor = editorbot
+    editor.textCursor().insertText('foo')
+    qtbot.keyClicks(editor, '(')
+    assert editor.toPlainText() == 'foo()'
+    assert editor.textCursor().columnNumber() == 4
+
+def test_editor_complete_bracket_nested(editorbot):
+    qtbot, editor = editorbot
+    editor.textCursor().insertText('foo(bar)')
+    editor.move_cursor(-1)
+    qtbot.keyClicks(editor, '(')
+    assert editor.toPlainText() == 'foo(bar())'
+    assert editor.textCursor().columnNumber() == 8
+
+def test_editor_bracket_closing(editorbot):
+    qtbot, editor = editorbot
+    editor.textCursor().insertText('foo(bar(x')
+    qtbot.keyClicks(editor, ')')
+    assert editor.toPlainText() == 'foo(bar(x)'
+    assert editor.textCursor().columnNumber() == 10
+    qtbot.keyClicks(editor, ')')
+    assert editor.toPlainText() == 'foo(bar(x))'
+    assert editor.textCursor().columnNumber() == 11
+    # same ')' closing with existing brackets starting at 'foo(bar(x|))'
+    editor.move_cursor(-2)
+    qtbot.keyClicks(editor, ')')
+    assert editor.toPlainText() == 'foo(bar(x))'
+    assert editor.textCursor().columnNumber() == 10
+    qtbot.keyClicks(editor, ')')
+    assert editor.toPlainText() == 'foo(bar(x))'
+    assert editor.textCursor().columnNumber() == 11
+