Repository: spyder-ide/spyder

Issue:
Can't launch Spyder if Completions plugin is deactivated
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->
Since #15259 was merged, spyder fails to launch with the following traceback.

This only happens when launching spyder with `from spyder.app.start import main; main()`, but somehow the bootstrap.py doesn't fail.

### Traceback
```python-traceback
Traceback (most recent call last):

  File "/Applications/Spyder.app/Contents/MacOS/Spyder", line 9, in <module>
    start.main()

  File "/Users/quentinpeter/pyscripts/spyder/spyder/app/start.py", line 227, in main
    mainwindow.main(options, args)

  File "/Users/quentinpeter/pyscripts/spyder/spyder/app/mainwindow.py", line 2311, in main
    mainwindow = create_window(app, splash, options, args)

  File "/Users/quentinpeter/pyscripts/spyder/spyder/app/mainwindow.py", line 2172, in create_window
    main.pre_visible_setup()

  File "/Users/quentinpeter/pyscripts/spyder/spyder/app/mainwindow.py", line 1209, in pre_visible_setup
    plugin_instance.before_mainwindow_visible()

  File "/Users/quentinpeter/pyscripts/spyder/spyder/plugins/statusbar/plugin.py", line 232, in before_mainwindow_visible
    self._organize_status_widgets()

  File "/Users/quentinpeter/pyscripts/spyder/spyder/plugins/statusbar/plugin.py", line 219, in _organize_status_widgets
    StatusBarWidgetPosition.Left, self.INTERNAL_WIDGETS[id_])

KeyError: 'lsp_status'
```

## Versions

* Spyder version: 5.1.0.dev0 44403bb0e
* Python version: 3.8.6
* Qt version: 5.12.10
* PyQt5 version: 5.12.3
* Operating System: Darwin 20.3.0

### Dependencies

```

# Mandatory:
applaunchservices >=0.1.7     :  0.2.1 (OK)
atomicwrites >=1.2.0          :  1.4.0 (OK)
chardet >=2.0.0               :  3.0.4 (OK)
cloudpickle >=0.5.0           :  1.6.0 (OK)
cookiecutter >=1.6.0          :  1.7.2 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.1.0 (OK)
IPython >=7.6.0               :  7.22.0 (OK)
jedi =0.17.2                  :  0.17.2 (OK)
jsonschema >=3.2.0            :  3.2.0 (OK)
keyring >=17.0.0              :  21.4.0 (OK)
nbconvert >=4.0               :  6.0.7 (OK)
numpydoc >=0.6.0              :  1.1.0 (OK)
parso =0.7.0                  :  0.7.0 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.7.2 (OK)
pygments >=2.0                :  2.7.1 (OK)
pylint >=1.0                  :  2.6.0 (OK)
pyls >=0.36.2;<1.0.0          :  0.36.2 (OK)
pyls_black >=0.4.6            :  0.4.6 (OK)
pyls_spyder >=0.3.2           :  0.3.2 (OK)
qdarkstyle =3.0.2             :  3.0.2 (OK)
qstylizer >=0.1.10            :  0.1.10 (OK)
qtawesome >=1.0.2             :  1.0.2 (OK)
qtconsole >=5.0.3             :  5.1.0.dev0 (OK)
qtpy >=1.5.0                  :  1.9.0 (OK)
setuptools >=39.0.0           :  50.3.0 (OK)
sphinx >=0.6.6                :  3.2.1 (OK)
spyder_kernels >=2.0.1;<2.1.0 :  2.1.0.dev0 (OK)
textdistance >=4.2.0          :  4.2.0 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3;<2.0.0      :  0.10.3 (OK)
zmq >=17                      :  19.0.2 (OK)

# Optional:
cython >=0.21                 :  0.29.21 (OK)
matplotlib >=2.0.0            :  3.3.2 (OK)
numpy >=1.7                   :  1.19.2 (OK)
pandas >=1.1.1                :  1.1.3 (OK)
scipy >=0.17.0                :  1.5.2 (OK)
sympy >=0.7.3                 :  1.6.2 (OK)
```



Discussion:
I can launch spyder if I replace my start script from:
```
from spyder.app import start
start.main()
```

to:

```
import pkg_resources
import os

# Needed for find_internal_plugins to work 
os.environ['SPYDER_DEV'] = 'True'


# Register completion providers
fallback = pkg_resources.EntryPoint.parse(
    'fallback = spyder.plugins.completion.providers.fallback.provider:'
    'FallbackProvider')

snippets = pkg_resources.EntryPoint.parse(
    'snippets = spyder.plugins.completion.providers.snippets.provider:'
    'SnippetsProvider'
)

lsp = pkg_resources.EntryPoint.parse(
    'lsp = spyder.plugins.completion.providers.languageserver.provider:'
    'LanguageServerProvider'
)

kite = pkg_resources.EntryPoint.parse(
    'kite = spyder.plugins.completion.providers.kite.provider:'
    'KiteProvider'
)

# Create a fake Spyder distribution
d = pkg_resources.Distribution(__file__)

# Add the providers to the fake EntryPoint
d._ep_map = {
    'spyder.completions': {
        'fallback': fallback,
        'snippets': snippets,
        'lsp': lsp,
        'kite': kite
    }
}

# Add the fake distribution to the global working_set
pkg_resources.working_set.add(d, 'spyder')


from spyder.app import start
start.main()
```
This clearly depends on the way you're launching Spyder, so it's not something we need to fix. It seems before you were not using our code completion architecture at all.

Fortunately, you were able to find a solution for it.
This worked in spyder 4. It is quite useful when debugging or profiling spyder to have a one liner to launch it.
@steff456, we need to catch the KeyError and pass in case the Completions plugin is deactivated.

Also, if the Language server provider is off, we shouldn't show its status bar widget. Right now it shows like this:

![imagen](https://user-images.githubusercontent.com/365293/115273136-cd39f200-a104-11eb-86fc-5468f7caef2c.png)

which makes little sense.

Resolving patch:
diff --git a/spyder/plugins/completion/container.py b/spyder/plugins/completion/container.py
--- a/spyder/plugins/completion/container.py
+++ b/spyder/plugins/completion/container.py
@@ -21,6 +21,7 @@ class CompletionContainer(PluginMainContainer):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.statusbar_widgets = {}
+        self.provider_statusbars = {}
 
     def setup(self, options=None):
         pass
@@ -36,14 +37,26 @@ def show_widget(self, Widget):
                 widget.sig_restart_spyder.connect(self.sig_restart_requested)
             widget.exec_()
 
-    def register_statusbar_widgets(self, statusbar_classes):
+    def register_statusbar_widgets(self, statusbar_classes, provider_name):
+        current_ids = []
         for StatusBar in statusbar_classes:
             statusbar = StatusBar(self)
             self.statusbar_widgets[statusbar.ID] = statusbar
+            current_ids.append(statusbar.ID)
+        self.provider_statusbars[provider_name] = current_ids
+        return current_ids
 
     def all_statusbar_widgets(self):
         return [self.statusbar_widgets[k] for k in self.statusbar_widgets]
 
+    def remove_statusbar_widget(self, status_key):
+        """Remove statusbar widget given its key."""
+        self.statusbar_widgets.pop(status_key, None)
+
+    def get_provider_statusbar_keys(self, provider_name):
+        """Get the list of statusbar keys for the given provider."""
+        return self.provider_statusbars[provider_name]
+
     def statusbar_rpc(self, status_key: str, method: str, args: tuple,
                       kwargs: dict):
         """
@@ -60,6 +73,7 @@ def statusbar_rpc(self, status_key: str, method: str, args: tuple,
         kwargs: dict
             Optional arguments of the method call.
         """
-        statusbar = self.statusbar_widgets[status_key]
-        call = getattr(statusbar, method)
-        call(*args, **kwargs)
+        if status_key in self.statusbar_widgets:
+            statusbar = self.statusbar_widgets[status_key]
+            call = getattr(statusbar, method)
+            call(*args, **kwargs)
diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py
--- a/spyder/plugins/completion/plugin.py
+++ b/spyder/plugins/completion/plugin.py
@@ -236,7 +236,7 @@ def __init__(self, parent, configuration=None):
                 raise e
 
         # Register statusbar widgets
-        self.register_statusbar_widgets()
+        self.register_statusbar_widgets(plugin_loaded=False)
 
         # Define configuration page and tabs
         (conf_providers, conf_tabs) = self.gather_providers_and_configtabs()
@@ -262,10 +262,10 @@ def register(self):
         preferences.register_plugin_preferences(self)
 
         container = self.get_container()
-        statusbar = self.get_plugin(Plugins.StatusBar)
-        if statusbar:
+        self.statusbar = self.get_plugin(Plugins.StatusBar)
+        if self.statusbar:
             for sb in container.all_statusbar_widgets():
-                statusbar.add_status_widget(sb)
+                self.statusbar.add_status_widget(sb)
 
         if self.main:
             self.main.sig_pythonpath_changed.connect(
@@ -322,8 +322,10 @@ def after_configuration_update(self, options: List[Union[tuple, str]]):
                         ('enabled_providers', provider_name))
                     if provider_status:
                         self.start_provider_instance(provider_name)
+                        self.register_statusbar_widget(provider_name)
                     else:
                         self.shutdown_provider_instance(provider_name)
+                        self.unregister_statusbar(provider_name)
                 elif option_name == 'provider_configuration':
                     providers_to_update |= {provider_name}
 
@@ -381,12 +383,59 @@ def on_mainwindow_visible(self):
             provider_info['instance'].on_mainwindow_visible()
 
     # ---------------------------- Status bar widgets -------------------------
-    def register_statusbar_widgets(self):
-        """Register status bar widgets for all providers with the container."""
-        container = self.get_container()
+    def register_statusbar_widgets(self, plugin_loaded=True):
+        """
+        Register status bar widgets for all providers with the container.
+
+        Parameters
+        ----------
+        plugin_loaded: bool
+            True if the plugin is already loaded in Spyder, False if it is
+            being loaded. This is needed to avoid adding statusbar widgets
+            multiple times at startup.
+        """
         for provider_key in self.providers:
-            provider = self.providers[provider_key]['instance']
-            container.register_statusbar_widgets(provider.STATUS_BAR_CLASSES)
+            provider_on = self.get_conf(
+                ('enabled_providers', provider_key), True)
+            if provider_on:
+                self.register_statusbar_widget(
+                    provider_key, plugin_loaded=plugin_loaded)
+
+    def register_statusbar_widget(self, provider_name, plugin_loaded=True):
+        """
+        Register statusbar widgets for a given provider.
+
+        Parameters
+        ----------
+        provider_name: str
+            Name of the provider that is going to create statusbar widgets.
+        plugin_loaded: bool
+            True if the plugin is already loaded in Spyder, False if it is
+            being loaded.
+        """
+        container = self.get_container()
+        provider = self.providers[provider_name]['instance']
+        widgets_ids = container.register_statusbar_widgets(
+            provider.STATUS_BAR_CLASSES, provider_name)
+        if plugin_loaded:
+            for id_ in widgets_ids:
+                currrent_widget = container.statusbar_widgets[id_]
+                self.statusbar.add_status_widget(currrent_widget)
+
+    def unregister_statusbar(self, provider_name):
+        """
+        Unregister statusbar widgets for a given provider.
+
+        Parameters
+        ----------
+        provider_name: str
+            Name of the provider that is going to delete statusbar widgets.
+        """
+        provider_keys = self.get_container().get_provider_statusbar_keys(
+            provider_name)
+        for id_ in provider_keys:
+            self.get_container().remove_statusbar_widget(id_)
+            self.statusbar.remove_status_widget(id_)
 
     # -------- Completion provider initialization redefinition wrappers -------
     def gather_providers_and_configtabs(self):
diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py
--- a/spyder/plugins/statusbar/plugin.py
+++ b/spyder/plugins/statusbar/plugin.py
@@ -215,9 +215,11 @@ def _organize_status_widgets(self):
 
         # Add the internal widgets in the desired layout
         for id_ in internal_layout:
-            self._statusbar.insertPermanentWidget(
-                StatusBarWidgetPosition.Left, self.INTERNAL_WIDGETS[id_])
-            self.INTERNAL_WIDGETS[id_].setVisible(True)
+            # This is needed in the case kite is installed but not enabled
+            if id_ in self.INTERNAL_WIDGETS:
+                self._statusbar.insertPermanentWidget(
+                    StatusBarWidgetPosition.Left, self.INTERNAL_WIDGETS[id_])
+                self.INTERNAL_WIDGETS[id_].setVisible(True)
 
         # Add the external left widgets
         for id_ in external_left:


Test patch: