Repository: spyder-ide/spyder
Pull request: PR: Fix on the fly completions inside braces
<!--- Make sure to read the Contributing Guidelines:                   --->
<!--- https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md --->
<!--- and follow PEP 8, PEP 257 and Spyder's code style:               --->
<!--- https://github.com/spyder-ide/spyder/wiki/Dev:-Coding-Style      --->

## Description of Changes


* [x] Wrote at least one-line docstrings (for any new functions)
* [x] Added unit test(s) covering the changes (if testable)
<!--- Remember that an image/animation is worth a thousand words! --->
* [x] Included a screenshot or animation (if affecting the UI, see [Licecap](https://www.cockos.com/licecap/))

![ipython](https://user-images.githubusercontent.com/3627835/67543080-434acd80-f6b5-11e9-8fd7-e9347af0fe73.gif)

<!--- Explain what you've done and why --->

Word under cursor selection fails (Qt issue 🤷‍♂?) if the cursor is next to a parens. Added a workaround for this and a test.


### Issue(s) Resolved

<!--- List the issue(s) below, in the form "Fixes #1234"; one per line --->

Fixes #10448


### Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the [Developer Certificate of Origin](https://developercertificate.org)
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

<!--- TYPE YOUR USER/NAME AFTER THE FOLLOWING: --->
I certify the above statement is true and correct: @goanpeca 

<!--- Thanks for your help making Spyder better for everyone! --->


Problem statement:
Autocompletions not working inside function calls
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->


## Problem Description
Autocompletions for variables don't work inside function calls.


### What steps reproduce the problem?

1. Start with the file:
```python
my_list = [1, 2, 3]
my_list_copy = list(my_$)
```
2. Observe that no autocompletions are shown.
3. However, if you manually request completions via `ctrl + space`, you will see the completions for `my_list` and `my_list_copy`.

### What is the expected output? What do you see instead?
__Expected:__ With autocompletions enabled, I expect to see the completion for `my_list`.
__Actual:__ No autocompletions are shown, even though manual completions work.


## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 4.0.0.dev0 (Commit: c2b7673b1)
* Python version: 3.7.4 64-bit
* Qt version: Qt 5.9.6
* PyQt version: PyQt5 5.9.2
* Operating System name/version: Darwin 18.7.0


### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
cloudpickle >=0.5.0          :  1.2.2 (OK)
pygments >=2.0               :  2.4.2 (OK)
qtconsole >=4.5.5            :  4.5.5 (OK)
nbconvert >=4.0              :  5.6.0 (OK)
sphinx >=0.6.6               :  2.2.0 (OK)
pylint >=0.25                :  2.4.2 (OK)
psutil >=0.3                 :  5.6.3 (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.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
numpydoc >=0.6.0             :  0.9.1 (OK)
spyder_kernels >=1.5.0;<2.0.0:  1.6.0 (OK)
qdarkstyle >=2.7             :  2.7 (OK)
atomicwrites >=1.2.0         :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
watchdog                     :  None (OK)
keyring                      :  None (OK)
pexpect >=4.4.0              :  4.7.0 (OK)
pympler                      :  None (OK)
sympy >=0.7.3                :  None (NOK)
cython >=0.21                :  None (NOK)
IPython >=4.0                :  7.8.0 (OK)
matplotlib >=2.0.0           :  None (NOK)
pandas >=0.13.1              :  None (NOK)
numpy >=1.7                  :  None (NOK)
scipy >=0.17.0               :  None (NOK)
pyls >=0.28.2;<0.29.0        :  0.28.3 (OK)
rtree >=0.8.3                :  0.8.3 (OK)
```

Hints from issue discussion:
Working on this!

Commit message: Fix test comments

Patch to review:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
index a5ca85fa026..07971a346c0 100644
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -3624,16 +3624,25 @@ def insert_text(event):
     def _handle_completions(self):
         """Handle on the fly completions with a delay."""
         cursor = self.textCursor()
+        pos = cursor.position()
         cursor.select(QTextCursor.WordUnderCursor)
         text = to_text_string(cursor.selectedText())
 
+        # WordUnderCursor fails if the cursor is next to a parens `)`.
+        # If the returned text starts with a right parens we move to the left.
+        if text.startswith((')', ']', '}')):
+            cursor.setPosition(pos - 1, QTextCursor.MoveAnchor)
+            cursor.select(QTextCursor.WordUnderCursor)
+            text = to_text_string(cursor.selectedText())
+
         if (len(text) >= self.automatic_completions_after_chars
                 and self._last_key_pressed_text):
             self.document_did_change(text)
 
             # Perform completion on the fly
             if self.automatic_completions and not self.in_comment_or_string():
-                if text.isalpha():
+                # Variables can include numbers and underscores
+                if text.isalpha() or text.isalnum() or '_' in text:
                     self.do_completion(automatic=True)
                     self._last_key_pressed_text = ''
 
diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py
index 79df93d10fd..6d5b191a776 100644
--- a/spyder/plugins/editor/widgets/tests/test_introspection.py
+++ b/spyder/plugins/editor/widgets/tests/test_introspection.py
@@ -199,6 +199,73 @@ def test_automatic_completions(lsp_codeeditor, qtbot):
     code_editor.toggle_code_snippets(True)
 
 
+@pytest.mark.slow
+@flaky(max_runs=3)
+def test_automatic_completions_parens_bug(lsp_codeeditor, qtbot):
+    """
+    Test on-the-fly completions.
+
+    Autocompletions for variables don't work inside function calls.
+
+    See: spyder-ide/spyder#10448
+    """
+    code_editor, _ = lsp_codeeditor
+    completion = code_editor.completion_widget
+    code_editor.toggle_code_snippets(False)
+
+    # Parens:
+    # Set cursor to start
+    code_editor.set_text('my_list = [1, 2, 3]\nlist_copy = list((my))')
+    cursor = code_editor.textCursor()
+    code_editor.moveCursor(cursor.End)
+
+    # Move cursor next to list((my$))
+    qtbot.keyPress(code_editor, Qt.Key_Left)
+    qtbot.keyPress(code_editor, Qt.Key_Left)
+    qtbot.wait(500)
+
+    # Complete my_ -> my_list
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=5000) as sig:
+        qtbot.keyClicks(code_editor, '_')
+
+    assert "my_list" in [x['label'] for x in sig.args[0]]
+
+    # Square braces:
+    # Set cursor to start
+    code_editor.set_text('my_dic = {1: 1, 2: 2}\nonesee = 1\none = my_dic[on]')
+    cursor = code_editor.textCursor()
+    code_editor.moveCursor(cursor.End)
+
+    # Move cursor next to my_dic[on$]
+    qtbot.keyPress(code_editor, Qt.Key_Left)
+    qtbot.wait(500)
+
+    # Complete one -> onesee
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=5000) as sig:
+        qtbot.keyClicks(code_editor, 'e')
+
+    assert "onesee" in [x['label'] for x in sig.args[0]]
+
+    # Curly braces:
+    # Set cursor to start
+    code_editor.set_text('my_dic = {1: 1, 2: 2}\nonesee = 1\none = {on}')
+    cursor = code_editor.textCursor()
+    code_editor.moveCursor(cursor.End)
+
+    # Move cursor next to {on*}
+    qtbot.keyPress(code_editor, Qt.Key_Left)
+    qtbot.wait(500)
+
+    # Complete one -> onesee
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=5000) as sig:
+        qtbot.keyClicks(code_editor, 'e')
+
+    assert "onesee" in [x['label'] for x in sig.args[0]]
+
+
 @pytest.mark.slow
 @pytest.mark.first
 @flaky(max_runs=5)


Code review comments:
diff_hunk: @@ -3624,16 +3624,25 @@ def insert_text(event):
     def _handle_completions(self):
         """Handle on the fly completions with a delay."""
         cursor = self.textCursor()
+        pos = cursor.position()
         cursor.select(QTextCursor.WordUnderCursor)
         text = to_text_string(cursor.selectedText())
 
+        # WordUnderCursor fails if the cursor is next to a parens `)`.
+        # If the returned text starts with a right parens we move to the left.
path: spyder/plugins/editor/widgets/codeeditor.py
text: @user1:
Please change this comment to

    # WordUnderCursor fails if the cursor is next to a right brace.
    # If the returned text starts with it, we move to the left.