Repository: spyder-ide/spyder

Issue:
Cannot connect to an external PyLS server
<!--- **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! --->
## Problem Description

The new menu does not allow to connect to an external PyLS server. 

### What steps reproduce the problem?

1. Run spyder-pyls server on one terminal with the command `python -m pyls -vvv --port 2089 --tcp`
2. Run spyder on another terminal
3. Try to connect with the running server by changing the port in Preferences > Completion and linting > Advanced 
4. After giving apply, there's no new connection received by the local pyls server.
* The same happens after trying to setup a new server for python.

### What is the expected output? What do you see instead?
Connect to an external PyLS server running locally.

## 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: Spyder 4.0.0.dev0 (Commit: ba26876de) 
* Python version: 3.6.8
* Qt version: 5.12.2
* PyQt version: 5.12.1 
* Operating System name/version: Darwin 18.2.0 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
pygments >=2.0    :  2.3.1 (OK)
qdarkstyle >=2.6.4:  2.6.5 (OK)
sphinx >=0.6.6    :  1.8.5 (OK)
psutil >=0.3      :  5.6.1 (OK)
pyls >=0.19.0     :  0.21.5 (OK)
nbconvert >=4.0   :  5.4.1 (OK)
pandas >=0.13.1   :  None (NOK)
numpy >=1.7       :  1.16.2 (OK)
sympy >=0.7.3     :  None (NOK)
cython >=0.21     :  None (NOK)
qtconsole >=4.2.0 :  4.4.3 (OK)
IPython >=4.0     :  7.4.0 (OK)
matplotlib >=2.0.0:  None (NOK)
pylint >=0.25     :  2.3.1 (OK)
```



Discussion:
@dalthviz, I was wrong when I instructed you to assume that external servers can only live in external machines. As @steff456 pointed out, external servers are simply servers started outside Spyder.

So to fix this we need to add a new checkbox in `Completion and linting > Advanced Setttings` (below the current options) with this text:

    [ ] This is an external server

associated to the option `advanced/external` and being `False` by default. Then we need to use that option to set the value of `self.python_config['external']` in `update_python_config` of `LSPManager`.

Resolving patch:
diff --git a/spyder/config/main.py b/spyder/config/main.py
--- a/spyder/config/main.py
+++ b/spyder/config/main.py
@@ -698,6 +698,7 @@
               'advanced/command_launch': 'pyls',
               'advanced/host': '127.0.0.1',
               'advanced/port': 2087,
+              'advanced/external': False
              })
             ]
 
diff --git a/spyder/plugins/editor/lsp/manager.py b/spyder/plugins/editor/lsp/manager.py
--- a/spyder/plugins/editor/lsp/manager.py
+++ b/spyder/plugins/editor/lsp/manager.py
@@ -43,7 +43,6 @@ def __init__(self, parent):
         self.clients = {}
         self.requests = {}
         self.register_queue = {}
-        self.python_config = PYTHON_CONFIG.copy()
 
         # Register languages to create clients for
         for language in self.get_languages():
@@ -81,8 +80,7 @@ def get_languages(self):
     def get_language_config(self, language):
         """Get language configuration options from our config system."""
         if language == 'python':
-            self.update_python_config()
-            return self.python_config
+            return self.generate_python_config()
         else:
             return self.get_option(language)
 
@@ -229,11 +227,13 @@ def send_request(self, language, request, params):
                 client = self.clients[language]['instance']
                 client.perform_request(request, params)
 
-    def update_python_config(self):
+    def generate_python_config(self):
         """
         Update Python server configuration with the options saved in our
         config system.
         """
+        python_config = PYTHON_CONFIG.copy()
+
         # Server options
         cmd = self.get_option('advanced/command_launch')
         host = self.get_option('advanced/host')
@@ -301,18 +301,20 @@ def update_python_config(self):
             'follow_imports': self.get_option('jedi_definition/follow_imports')
         }
 
+        # Advanced
+        external_server = self.get_option('advanced/external')
+
         # Setup options in json
-        self.python_config['cmd'] = cmd
+        python_config['cmd'] = cmd
         if host in self.LOCALHOST:
-            self.python_config['args'] = '--host {host} --port {port} --tcp'
-            self.python_config['external'] = False
+            python_config['args'] = '--host {host} --port {port} --tcp'
         else:
-            self.python_config['args'] = ''
-            self.python_config['external'] = True
-        self.python_config['host'] = host
-        self.python_config['port'] = port
+            python_config['args'] = ''
+        python_config['external'] = external_server
+        python_config['host'] = host
+        python_config['port'] = port
 
-        plugins = self.python_config['configurations']['pyls']['plugins']
+        plugins = python_config['configurations']['pyls']['plugins']
         plugins['pycodestyle'] = pycodestyle
         plugins['pyflakes'] = pyflakes
         plugins['pydocstyle'] = pydocstyle
@@ -320,3 +322,5 @@ def update_python_config(self):
         plugins['jedi_signature_help'] = jedi_signature_help
         plugins['preload']['modules'] = self.get_option('preload_modules')
         plugins['jedi_definition'] = jedi_definition
+
+        return python_config
diff --git a/spyder/preferences/languageserver.py b/spyder/preferences/languageserver.py
--- a/spyder/preferences/languageserver.py
+++ b/spyder/preferences/languageserver.py
@@ -862,6 +862,9 @@ def setup_page(self):
             word_wrap=False)
         advanced_port = self.create_spinbox(
             ":", "", 'advanced/port', min_=1, max_=65535, step=1)
+        external_server = self.create_checkbox(
+            _("This is an external server"),
+            'advanced/external')
 
         # Advanced layout
         advanced_g_layout = QGridLayout()
@@ -879,6 +882,7 @@ def setup_page(self):
         advanced_layout = QVBoxLayout()
         advanced_layout.addWidget(advanced_label)
         advanced_layout.addLayout(advanced_g_layout)
+        advanced_layout.addWidget(external_server)
         advanced_widget.setLayout(advanced_layout)
 
         # --- Other servers tab ---


Test patch: