Repository: spyder-ide/spyder

Issue:
KeyError textDocumentSync when opening Spyder with a project
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

- Close Spyder with a project open by clicking the "X" in the top right corner
- Open Spyder. Previous project opens.
- Issue reporter appears before the main window.

### Traceback
```python-traceback
  File "/home/keoughs/anaconda3/lib/python3.7/site-packages/spyder/plugins/editor/lsp/client.py", line 306, in on_msg_received
    handler(resp['params'])
  File "/home/keoughs/anaconda3/lib/python3.7/site-packages/spyder/plugins/editor/lsp/client.py", line 365, in process_server_capabilities
    if isinstance(server_capabilites['textDocumentSync'], int):
KeyError: 'textDocumentSync'
```

## Versions

* Spyder version: 4.0.0b3 
* Python version: 3.7.3
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Linux 2.6.32-754.14.2.el6.x86_64

### Dependencies

```
pygments >=2.0    :  2.4.2 (OK)
qdarkstyle >=2.6.4:  2.7 (OK)
sphinx >=0.6.6    :  2.1.2 (OK)
psutil >=0.3      :  5.6.3 (OK)
pyls >=0.27.0     :  0.27.0 (OK)
nbconvert >=4.0   :  5.5.0 (OK)
pandas >=0.13.1   :  0.24.2 (OK)
numpy >=1.7       :  1.16.4 (OK)
sympy >=0.7.3     :  1.4 (OK)
cython >=0.21     :  0.29.11 (OK)
qtconsole >=4.5.0 :  4.5.1 (OK)
IPython >=4.0     :  7.6.0 (OK)
matplotlib >=2.0.0:  3.1.0 (OK)
pylint >=0.25     :  2.3.1 (OK)
```



Discussion:
I don't really understand what this is doing here, it's probably better to fix why 'textDocumentSync' isn't being found in server_capabilities, but it seems to me you could patch this to avoid a traceback by using `get` with a default.
```
if isinstance(server_capabilites.get('textDocumentSync', None), int):
```

I have made a few potential fixes for this on my fork, happy to submit a PR if you think what I've done is suitable, but all they do is prevent the exception being raised, so it isn't actually fixing what might be broken.

I have assumed that having `textDocumentSync` not set in `server_capabilities` is ok, since the existing code removes it if it's present but set to `None`.
```
if server_capabilites['textDocumentSync'] is None:
            server_capabilites.pop('textDocumentSync')
```

I've noticed in master, this file has moved since beta3 from `spyder/plugins/editor/lsp/client.py` to `spyder/plugins/languageserver/client.py`, but the code is the same, starting from line 368

Option 1: EAFP - "Pythonic"
```
        try:
            if isinstance(server_capabilites['textDocumentSync'], int):
                kind = server_capabilites['textDocumentSync']
                server_capabilites['textDocumentSync'] = TEXT_DOCUMENT_SYNC_OPTIONS
                server_capabilites['textDocumentSync']['change'] = kind
            if server_capabilites['textDocumentSync'] is None:
                server_capabilites.pop('textDocumentSync')
        except KeyError:
            pass
```
The downside of option 1 is that it could catch a KeyError that it shouldn't if "change" is not a key inside TEXT_DOCUMENT_SYNC_OPTIONS. This should never be the case, but it is a potential fail point in this approach.

Option 2: LBYL
```
        if 'textDocumentSync' in server_capabilities:
            if isinstance(server_capabilites['textDocumentSync'], int):
                kind = server_capabilites['textDocumentSync']
                server_capabilites['textDocumentSync'] = TEXT_DOCUMENT_SYNC_OPTIONS
                server_capabilites['textDocumentSync']['change'] = kind
            if server_capabilites['textDocumentSync'] is None:
                server_capabilites.pop('textDocumentSync')
```

Option 3: Use `.get()` with defaults that fail the test
```
        if isinstance(server_capabilites.get('textDocumentSync', None), int):
            kind = server_capabilites['textDocumentSync']
            server_capabilites['textDocumentSync'] = TEXT_DOCUMENT_SYNC_OPTIONS
            server_capabilites['textDocumentSync']['change'] = kind
        if server_capabilites.get('textDocumentSync', '') is None:
            server_capabilites.pop('textDocumentSync')
```

Any of these will prevent the KeyError exception being raised, I would go with Option1 personally.
However, if textDocumentSync _should_ actually be there and not having it will break something, then that is of course a bigger issue.
@Keou0007, could you upload a simple that shows this behavior? I can't reproduce it on my end.

@andfoy, do you know what could be happening here?
@Keou0007, please also run

    spyder --reset

in a terminal to see if that fixes the error for you.
> However, if textDocumentSync should actually be there and not having it will break something, then that is of course a bigger issue.

@Keou0007, this is actually the case, every LSP should send their textDocumentSync configurations, they are essential and mandatory. Thus, this points to some error in the server.

Could you please send us a copy of the lsp logs? They are found under ``~/.config/spyder-py3/lsp-logs``
@Keou0007, to generate those logs, you need to start Spyder with the following option from the command line:

    spyder --debug-info verbose

Then you can upload here the two log files present in `~/.config/spyder-py3-dev/lsp-logs` (i.e. `client_python.log` and `server_python.log`).

Thanks for your help!
I'll set up my boot method to enable verbose debugging and grab them next time I see the traceback. It's less consistent now than it was with beta2, so it might take a few days.

Resolving patch:
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
@@ -893,10 +893,11 @@ def parse_lsp_config(self, config):
         completion_options = config['completionProvider']
         signature_options = config['signatureHelpProvider']
         range_formatting_options = config['documentOnTypeFormattingProvider']
-        self.open_close_notifications = sync_options['openClose']
-        self.sync_mode = sync_options['change']
-        self.will_save_notify = sync_options['willSave']
-        self.will_save_until_notify = sync_options['willSaveWaitUntil']
+        self.open_close_notifications = sync_options.get('openClose', False)
+        self.sync_mode = sync_options.get('change', TextDocumentSyncKind.NONE)
+        self.will_save_notify = sync_options.get('willSave', False)
+        self.will_save_until_notify = sync_options.get('willSaveWaitUntil',
+                                                       False)
         self.save_include_text = sync_options['save']['includeText']
         self.enable_hover = config['hoverProvider']
         self.auto_completion_characters = (


Test patch: