Repository: spyder-ide/spyder

Issue:
Autocomplete dict error
<!--- **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! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [x] Tried basic troubleshooting (if a bug/error)
    * [x] Restarted Spyder
    * [x] Reset preferences with ``spyder --reset``
    * [x] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [x] Tried the other applicable steps from the Troubleshooting Guide
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
When pressed TAB button to autocomplete and connected to an LSP server, spyder returns an error in cursor.setPosition(completion['textEdit']['range']['start'])


### What steps reproduce the problem?

1. Connect to an LSP server
2. Open a file to edit or new
3. Request autocomplete list
4. Insert autocomplete text

### What is the expected output? What do you see instead?
It is expected that the selected autocomplete option is inserted on the current line and character, but it returns an error instead


### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

Traceback (most recent call last):
  File "/home/hendrik/anaconda3/envs/fIDE5/lib/python3.8/site-packages/spyder/plugins/editor/widgets/completion.py", line 312, in keyPressEvent
    self.item_selected(item=item)
  File "/home/hendrik/anaconda3/envs/fIDE5/lib/python3.8/site-packages/spyder/plugins/editor/widgets/completion.py", line 434, in item_selected
    self.textedit.insert_completion(item.data(Qt.UserRole),
  File "/home/hendrik/anaconda3/envs/fIDE5/lib/python3.8/site-packages/spyder/plugins/editor/widgets/base.py", line 883, in insert_completion
    cursor.setPosition(completion['textEdit']['range']['start'])
TypeError: setPosition(self, int, mode: QTextCursor.MoveMode = QTextCursor.MoveAnchor): argument 1 has unexpected type 'dict'

```

## 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.1
* Python version: 3.8.1
* Qt version: 5.12.7
* PyQt version: 5.12.3
* Operating System name/version: Linux 5.3.0-29-generic

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
atomicwrites >=1.2.0         :  1.3.0 (OK)
chardet >=2.0.0              :  3.0.4 (OK)
cloudpickle >=0.5.0          :  1.3.0 (OK)
diff_match_patch >=20181111  :  20181111 (OK)
intervaltree                 :  None (OK)
IPython >=4.0                :  7.12.0 (OK)
jedi =0.14.1                 :  0.14.1 (OK)
keyring                      :  None (OK)
nbconvert >=4.0              :  5.6.1 (OK)
numpydoc >=0.6.0             :  0.9.2 (OK)
pexpect >=4.4.0              :  4.8.0 (OK)
pickleshare >=0.4            :  0.7.5 (OK)
psutil >=0.3                 :  5.6.7 (OK)
pygments >=2.0               :  2.5.2 (OK)
pylint >=0.25                :  2.4.4 (OK)
pyls >=0.31.2;<0.32.0        :  0.31.8 (OK)
xdg >=0.26                   :  0.26 (OK)
zmq >=17                     :  18.1.1 (OK)
qdarkstyle >=2.7             :  2.8 (OK)
qtawesome >=0.5.7            :  0.6.1 (OK)
qtconsole >=4.6.0            :  4.6.0 (OK)
qtpy >=1.5.0                 :  1.9.0 (OK)
rtree >=0.8.3                :  0.9.3 (OK)
sphinx >=0.6.6               :  2.4.0 (OK)
spyder_kernels >=1.8.1;<2.0.0:  1.8.1 (OK)
watchdog                     :  None (OK)
cython >=0.21                :  None (NOK)
matplotlib >=2.0.0           :  None (NOK)
numpy >=1.7                  :  None (NOK)
pandas >=0.13.1              :  None (NOK)
scipy >=0.17.0               :  None (NOK)
sympy >=0.7.3                :  1.5.1 (OK)
```



Discussion:
Thanks for reporting this problem. However, your description is not enough for us to reproduce this problem. In particular, this is extremely vague:

> Connect to an LSP server

What server (there are dozens of servers)? And how did you connect to it?
Sorry for the vague description, i thought that it would happen with any LSP server, because all of them uses the same protocol. The server that I am using is the one provided by my extension, the F server, it should be automatically configured if you install the extension. I am providing an link to download below.

[Download extension](https://drive.google.com/file/d/1OntIvcKiu0C7bsRXwYVNxMh1lsyeu0vm/view?usp=sharing)

Thank you for your reply.
> I am providing an link to download below.

Thanks!

@andfoy, please try to reproduce this one.
@hlouzada Thanks for the information, I'm wondering how are you implementing the ``textDocument/completion`` call
Hi @andfoy I am currently using the xtext with the lsp4j implementation for the language server. When `textDocument/completion` is called it should return something like this:

```
{
    'isIncomplete': True,
    'items': [{
        'label': 'shim',
        'kind': 14,
        'sortText': '00000',
        'textEdit': {
            'range': {
                'start': {'line': 24, 'character': 4},
                'end': {'line': 24, 'character': 5}
            },
            'newText': 'shim'
        }
    },{
        'label': 'start',
        'kind': 14,
        'sortText': '00001',
        'textEdit': {
            'range': {
                'start': {'line': 24, 'character': 4},
                'end': {'line': 24, 'character': 5}
            },
            'newText': 'start'
        }
    }]
}
```
> Thanks for the information

No problem, thank you for verifying this error.
@andfoy, please take a look at the last @hlouzada comment.

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/base.py b/spyder/plugins/editor/widgets/base.py
--- a/spyder/plugins/editor/widgets/base.py
+++ b/spyder/plugins/editor/widgets/base.py
@@ -878,9 +878,17 @@ def insert_completion(self, completion, completion_position):
         selection_start, selection_end = self.get_selection_start_end()
 
         if isinstance(completion, dict) and 'textEdit' in completion:
-            cursor.setPosition(completion['textEdit']['range']['start'])
-            cursor.setPosition(completion['textEdit']['range']['end'],
-                               QTextCursor.KeepAnchor)
+            completion_range = completion['textEdit']['range']
+            start = completion_range['start']
+            end = completion_range['end']
+            if isinstance(completion_range['start'], dict):
+                start_line, start_col = start['line'], start['character']
+                start = self.get_position_line_number(start_line, start_col)
+            if isinstance(completion_range['start'], dict):
+                end_line, end_col = end['line'], end['character']
+                end = self.get_position_line_number(end_line, end_col)
+            cursor.setPosition(start)
+            cursor.setPosition(end, QTextCursor.KeepAnchor)
             text = to_text_string(completion['textEdit']['newText'])
         else:
             text = completion
diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py
--- a/spyder/widgets/mixins.py
+++ b/spyder/widgets/mixins.py
@@ -767,6 +767,15 @@ def get_cursor_line_number(self):
         """Return cursor line number"""
         return self.textCursor().blockNumber()+1
 
+    def get_position_line_number(self, line, col):
+        """Get position offset from (line, col) coordinates."""
+        block = self.document().findBlockByNumber(line)
+        cursor = QTextCursor(block)
+        cursor.movePosition(QTextCursor.StartOfBlock)
+        cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor,
+                            n=col + 1)
+        return cursor.position()
+
     def set_cursor_position(self, position):
         """Set cursor position"""
         position = self.get_position(position)


Test patch: