Repository: spyder-ide/spyder

Issue:
Incorrect Kite completions ordering
Kite completions are not ordered correctly. See the following example in Spyder:

![image](https://user-images.githubusercontent.com/1977419/63958948-f98c9080-ca3f-11e9-8d02-ba37a956f8b7.png)

The completions ordering returned by the Kite engine should be maintained, as in the following example from VS Code:

<img width="524" alt="Screen Shot 2019-08-29 at 9 31 47 AM" src="https://user-images.githubusercontent.com/1977419/63958970-04dfbc00-ca40-11e9-8242-56ea6f587857.png">


Discussion:

Resolving patch:
diff --git a/spyder/plugins/completion/kite/providers/document.py b/spyder/plugins/completion/kite/providers/document.py
--- a/spyder/plugins/completion/kite/providers/document.py
+++ b/spyder/plugins/completion/kite/providers/document.py
@@ -91,7 +91,7 @@ def convert_completion_request(self, response):
                             completion['hint'], CompletionItemKind.TEXT),
                         'insertText': completion['snippet']['text'],
                         'filterText': completion['display'],
-                        'sortText': completion['display'][0],
+                        'sortText': completion['display'],
                         'documentation': completion['documentation']['text']
                     }
                     spyder_completions.append(entry)
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -986,12 +986,28 @@ def process_completion(self, params):
             cursor = self.textCursor()
             cursor.select(QTextCursor.WordUnderCursor)
             text = to_text_string(cursor.selectedText())
+            first_letter = text[0] if len(text) > 0 else ''
+            is_upper_word = first_letter.isupper()
             completions = [] if completions is None else completions
-            available_completions = {x['insertText']: x for x in completions}
+            available_completions = {x['insertText'].strip():
+                                     x for x in completions[::-1]}
             available_completions.pop(text, False)
             completions = list(available_completions.values())
 
             if completions is not None and len(completions) > 0:
+                for completion in completions:
+                    sort_text = completion['sortText']
+                    if is_upper_word:
+                        first_sort_letter = sort_text[1]
+                        if first_sort_letter.islower():
+                            if first_sort_letter.upper() == first_letter:
+                                completion['sortText'] = 'z' + sort_text
+                    else:
+                        first_sort_letter = sort_text[1]
+                        if first_sort_letter.isupper():
+                            if first_sort_letter.lower() == first_letter:
+                                completion['sortText'] = 'z' + sort_text
+
                 completion_list = sorted(completions,
                                          key=lambda x: x['sortText'])
                 self.completion_widget.show_list(


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
@@ -355,6 +355,42 @@ def test_completions(lsp_codeeditor, qtbot):
     code_editor.toggle_automatic_completions(True)
 
 
+@pytest.mark.slow
+@pytest.mark.first
+def test_completion_order(lsp_codeeditor, qtbot):
+    code_editor, _ = lsp_codeeditor
+    completion = code_editor.completion_widget
+
+    code_editor.toggle_automatic_completions(False)
+
+    # Set cursor to start
+    code_editor.go_to_line(1)
+    qtbot.keyClicks(code_editor, 'impo')
+    with qtbot.waitSignal(code_editor.lsp_response_signal, timeout=30000):
+        code_editor.document_did_change()
+
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=10000) as sig:
+        qtbot.keyPress(code_editor, Qt.Key_Tab)
+
+    first_completion = sig.args[0][0]
+    assert first_completion['insertText'] == 'import'
+
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=10000) as sig:
+        qtbot.keyPress(code_editor, Qt.Key_Tab)
+
+    qtbot.keyPress(code_editor, Qt.Key_Enter, delay=300)
+    qtbot.keyClicks(code_editor, 'Impo')
+
+    with qtbot.waitSignal(completion.sig_show_completions,
+                          timeout=10000) as sig:
+        qtbot.keyPress(code_editor, Qt.Key_Tab)
+
+    first_completion = sig.args[0][0]
+    assert first_completion['insertText'] == 'ImportError'
+
+
 @pytest.mark.slow
 @pytest.mark.first
 @pytest.mark.skipif(not sys.platform.startswith('linux'),