# Buggy code
def process_completion(self, params):
    """Handle completion response."""
    args = self.completion_args
    if args is None:
        # This should not happen
        return
    self.completion_args = None
    position, automatic = args
    try:
        completions = params["params"]
        completions = [] if completions is None else completions

        replace_end = self.textCursor().position()
        under_cursor = self.get_current_word_and_position(completion=True)
        if under_cursor:
            word, replace_start = under_cursor
        else:
            word = ""
            replace_start = replace_end
        first_letter = ""
        if len(word) > 0:
            first_letter = word[0]

        def sort_key(completion):
            if "textEdit" in completion:
                first_insert_letter = completion["textEdit"]["newText"][0]
            else:
                first_insert_letter = completion["insertText"][0]
            case_mismatch = (
                first_letter.isupper() and first_insert_letter.islower()
            ) or (first_letter.islower() and first_insert_letter.isupper())
            # False < True, so case matches go first
            return (case_mismatch, completion["sortText"])

        completion_list = sorted(completions, key=sort_key)

        # Allow for textEdit completions to be filtered by Spyder
        # if on-the-fly completions are disabled, only if the
        # textEdit range matches the word under the cursor.
        for completion in completion_list:
            if "textEdit" in completion:
                c_replace_start = completion["textEdit"]["range"]["start"]
                c_replace_end = completion["textEdit"]["range"]["end"]
                if c_replace_start == replace_start and c_replace_end == replace_end:
                    insert_text = completion["textEdit"]["newText"]
                    completion["filterText"] = insert_text
                    completion["insertText"] = insert_text
                    del completion["textEdit"]

        if len(completion_list) > 0:
            self.completion_widget.show_list(completion_list, position, automatic)

        self.kite_call_to_action.handle_processed_completions(completions)
    except RuntimeError:
        # This is triggered when a codeeditor instance has been
        # removed before the response can be processed.
        self.kite_call_to_action.hide_coverage_cta()
        return
    except Exception:
        self.log_lsp_handle_errors("Error when processing completions")


# Traceback (IndexError)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/editor/widgets/codeeditor.py", line 1086, in process_completion
completion_list = sorted(completions, key=sort_key)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/editor/widgets/codeeditor.py", line 1077, in sort_key
first_insert_letter = completion['insertText'][0]
IndexError: string index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/lib/python3.6/logging/__init__.py", line 994, in emit
msg = self.format(record)
File "/usr/lib/python3.6/logging/__init__.py", line 840, in format
return fmt.format(record)
File "/usr/lib/python3.6/logging/__init__.py", line 577, in format
record.message = record.getMessage()
File "/usr/lib/python3.6/logging/__init__.py", line 338, in getMessage
msg = msg % self.args
ValueError: incomplete format
Call stack:
File "/usr/local/bin/spyder3", line 10, in <module>
sys.exit(main())
File "/usr/local/lib/python3.6/dist-packages/spyder/app/start.py", line 195, in main
mainwindow.main()
File "/usr/local/lib/python3.6/dist-packages/spyder/app/mainwindow.py", line 3646, in main
mainwindow = run_spyder(app, options, args)
File "/usr/local/lib/python3.6/dist-packages/spyder/app/mainwindow.py", line 3529, in run_spyder
app.exec_()
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/completion/languageserver/client.py", line 343, in on_msg_received
handler(resp['result'], req_id)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/completion/languageserver/providers/document.py", line 109, in process_document_completion
LSPRequestTypes.DOCUMENT_COMPLETION, {'params': response})
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/completion/languageserver/plugin.py", line 279, in receive_response
self.COMPLETION_CLIENT_NAME, req_id, response)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/completion/plugin.py", line 138, in receive_response
self.gather_and_send(request_responses)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/completion/plugin.py", line 189, in gather_and_send
response_instance.handle_response(req_type, responses)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/editor/widgets/codeeditor.py", line 915, in handle_response
handler(params)
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/editor/widgets/codeeditor.py", line 1098, in process_completion
self.log_lsp_handle_errors('Error when processing completions')
File "/usr/local/lib/python3.6/dist-packages/spyder/plugins/editor/widgets/codeeditor.py", line 946, in log_lsp_handle_errors
logger.error('%', 1, stack_info=True)
Message: '%'
Arguments: (1,)

# Fixed code
def process_completion(self, params):
    """Handle completion response."""
    args = self.completion_args
    if args is None:
        # This should not happen
        return
    self.completion_args = None
    position, automatic = args
    try:
        completions = params["params"]
        completions = (
            []
            if completions is None
            else [completion for completion in completions if completion["insertText"]]
        )

        replace_end = self.textCursor().position()
        under_cursor = self.get_current_word_and_position(completion=True)
        if under_cursor:
            word, replace_start = under_cursor
        else:
            word = ""
            replace_start = replace_end
        first_letter = ""
        if len(word) > 0:
            first_letter = word[0]

        def sort_key(completion):
            if "textEdit" in completion:
                first_insert_letter = completion["textEdit"]["newText"][0]
            else:
                first_insert_letter = completion["insertText"][0]
            case_mismatch = (
                first_letter.isupper() and first_insert_letter.islower()
            ) or (first_letter.islower() and first_insert_letter.isupper())
            # False < True, so case matches go first
            return (case_mismatch, completion["sortText"])

        completion_list = sorted(completions, key=sort_key)

        # Allow for textEdit completions to be filtered by Spyder
        # if on-the-fly completions are disabled, only if the
        # textEdit range matches the word under the cursor.
        for completion in completion_list:
            if "textEdit" in completion:
                c_replace_start = completion["textEdit"]["range"]["start"]
                c_replace_end = completion["textEdit"]["range"]["end"]
                if c_replace_start == replace_start and c_replace_end == replace_end:
                    insert_text = completion["textEdit"]["newText"]
                    completion["filterText"] = insert_text
                    completion["insertText"] = insert_text
                    del completion["textEdit"]

        if len(completion_list) > 0:
            self.completion_widget.show_list(completion_list, position, automatic)

        self.kite_call_to_action.handle_processed_completions(completions)
    except RuntimeError:
        # This is triggered when a codeeditor instance has been
        # removed before the response can be processed.
        self.kite_call_to_action.hide_coverage_cta()
        return
    except Exception:
        self.log_lsp_handle_errors("Error when processing completions")