Repository: spyder-ide/spyder

Issue:
Completions are not working for underscore variables
If a method or variable starts with `_`, completions are not working:

```python
def _test1():
    pass


_test2 = 2
__test3 = 1

_*
```

Press `<tab>` on the cursor (*) and none of `_test1`, `_test2` `__test3` appear.





Discussion:
It was working on v4.0.0b4

<img width="370" alt="Screen Shot 2019-10-29 at 07 10 29" src="https://user-images.githubusercontent.com/3627835/67765917-47f7e480-fa1b-11e9-8ae5-02c74187e743.png">

Please take a look at this @goanpeca. Most probably the sorting algorithm for completion entries is now dropping underscores.
Will do. 

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/completion.py b/spyder/plugins/editor/widgets/completion.py
--- a/spyder/plugins/editor/widgets/completion.py
+++ b/spyder/plugins/editor/widgets/completion.py
@@ -30,25 +30,26 @@
 
 class CompletionWidget(QListWidget):
     """Completion list widget."""
-    ITEM_TYPE_MAP = {CompletionItemKind.TEXT: 'text',
-                     CompletionItemKind.METHOD: 'method',
-                     CompletionItemKind.FUNCTION: 'function',
-                     CompletionItemKind.CONSTRUCTOR: 'constructor',
-                     CompletionItemKind.FIELD: 'field',
-                     CompletionItemKind.VARIABLE: 'variable',
-                     CompletionItemKind.CLASS: 'class',
-                     CompletionItemKind.INTERFACE: 'interface',
-                     CompletionItemKind.MODULE: 'module',
-                     CompletionItemKind.PROPERTY: 'property',
-                     CompletionItemKind.UNIT: 'unit',
-                     CompletionItemKind.VALUE: 'value',
-                     CompletionItemKind.ENUM: 'enum',
-                     CompletionItemKind.KEYWORD: 'keyword',
-                     CompletionItemKind.SNIPPET: 'snippet',
-                     CompletionItemKind.COLOR: 'color',
-                     CompletionItemKind.FILE: 'filenew',
-                     CompletionItemKind.REFERENCE: 'reference',
-                     }
+    ITEM_TYPE_MAP = {
+        CompletionItemKind.TEXT: 'text',
+        CompletionItemKind.METHOD: 'method',
+        CompletionItemKind.FUNCTION: 'function',
+        CompletionItemKind.CONSTRUCTOR: 'constructor',
+        CompletionItemKind.FIELD: 'field',
+        CompletionItemKind.VARIABLE: 'variable',
+        CompletionItemKind.CLASS: 'class',
+        CompletionItemKind.INTERFACE: 'interface',
+        CompletionItemKind.MODULE: 'module',
+        CompletionItemKind.PROPERTY: 'property',
+        CompletionItemKind.UNIT: 'unit',
+        CompletionItemKind.VALUE: 'value',
+        CompletionItemKind.ENUM: 'enum',
+        CompletionItemKind.KEYWORD: 'keyword',
+        CompletionItemKind.SNIPPET: 'snippet',
+        CompletionItemKind.COLOR: 'color',
+        CompletionItemKind.FILE: 'filenew',
+        CompletionItemKind.REFERENCE: 'reference',
+        }
     ICON_MAP = {}
 
     sig_show_completions = Signal(object)
@@ -120,6 +121,7 @@ def show_list(self, completion_list, position, automatic):
         if not isinstance(completion_list[0], dict):
             self.is_internal_console = True
         self.completion_list = completion_list
+
         # Check everything is in order
         self.update_current(new=True)
 
@@ -351,10 +353,10 @@ def check_can_complete(self, filter_text, current_word):
         """Check if current_word matches filter_text."""
         if not filter_text:
             return True
+
         if not current_word:
             return True
-        if not filter_text[0].isalpha():
-            current_word = filter_text[0] + current_word
+
         return to_text_string(filter_text).lower().startswith(
                 to_text_string(current_word).lower())
 


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_introspection.py b/spyder/plugins/editor/widgets/tests/test_introspection.py
--- a/spyder/plugins/editor/widgets/tests/test_introspection.py
+++ b/spyder/plugins/editor/widgets/tests/test_introspection.py
@@ -284,6 +284,34 @@ def test_completions(lsp_codeeditor, qtbot):
     # Set cursor to start
     code_editor.go_to_line(1)
 
+    # Complete dunder imports from _ --> import _foo/_foom
+    qtbot.keyClicks(code_editor, 'from _')
+    with qtbot.waitSignal(code_editor.lsp_response_signal, timeout=30000):
+        code_editor.document_did_change()
+
+    # press tab and get completions
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=10000) as sig:
+        qtbot.keyPress(code_editor, Qt.Key_Tab)
+    assert "__future__" in [x['label'] for x in sig.args[0]]
+    code_editor.set_text('')  # Delete line
+    code_editor.go_to_line(1)
+
+    # Complete underscore variables
+    qtbot.keyClicks(code_editor, '_foo = 1;_foom = 2;_fo')
+    with qtbot.waitSignal(code_editor.lsp_response_signal, timeout=30000):
+        code_editor.document_did_change()
+
+    # press tab and get completions
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=10000) as sig:
+        qtbot.keyPress(code_editor, Qt.Key_Tab)
+    completions = [x['label'] for x in sig.args[0]]
+    assert "_foo" in completions
+    assert "_foom" in completions
+    code_editor.set_text('')  # Delete line
+    code_editor.go_to_line(1)
+
     # Complete import mat--> import math
     qtbot.keyClicks(code_editor, 'import mat')
     with qtbot.waitSignal(code_editor.lsp_response_signal, timeout=30000):